You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Canvas中goBack()撤销函数执行顺序异常问题求助

修复撤销操作的顺序问题

问题出在你把图片和线条的操作分别存在两个独立数组中,撤销时优先处理图片数组,完全忽略了操作的时间顺序。正确的做法是用统一的历史栈记录所有操作,按执行顺序存储,撤销时直接从栈顶取最后一次操作处理。

修改步骤:

  1. 替换独立数组为统一历史栈
    删除原有的drawnImageData、lines、deletedImageData、deletedLines数组,改用两个栈:

    • history:按时间顺序记录所有已执行的操作
    • undoStack:记录被撤销的操作,用于重做功能
  2. 修改操作添加逻辑

    • 拖放图片时,向history添加类型为image的操作对象
    • 绘制线条时,向history添加类型为line的操作对象
  3. 重写撤销/重做函数

    • goBack():弹出history最后一个操作,根据类型处理后放入undoStack
    • goFoward():弹出undoStack最后一个操作,放回history恢复操作
  4. 更新画布绘制逻辑
    遍历history数组,根据操作类型分别绘制图片和线条

修改后的核心代码:

// 替换原有独立数组为统一历史栈
const history = [];
const undoStack = [];

// 修改图片拖放逻辑
canvas.ondrop = (ev) => {
  const index = parseInt(ev.dataTransfer.getData('text/plain'));
  const img = draggableImages[index];
  // 向历史栈添加图片操作
  history.push({
    type: 'image',
    data: { img, x: ev.offsetX, y: ev.offsetY }
  });
};

// 修改线条绘制逻辑(mouseup事件)
canvas.addEventListener('mouseup', () => {
  if (isDrawing) {
    const x = endCircle.x;
    const y = endCircle.y;
    const circle = getNearestCircle(x, y);
    if (circle) {
      // 向历史栈添加线条操作
      history.push({
        type: 'line',
        data: { start: startCircle, end: circle }
      });
    }
    isDrawing = false;
    startCircle = null;
    endCircle = null;
  }
});

// 重写撤销函数
function goBack() {
  if (history.length === 0) return;
  
  const lastOp = history.pop();
  undoStack.push(lastOp);
  
  // 更新按钮状态
  back_button.disabled = history.length === 0;
  back_button.style.cursor = history.length === 0 ? 'not-allowed' : 'pointer';
  foward_button.disabled = false;
  foward_button.style.cursor = 'pointer';
  clear_button.disabled = history.length === 0;
  clear_button.style.cursor = history.length === 0 ? 'not-allowed' : 'pointer';
}

// 重写重做函数
function goFoward() {
  if (undoStack.length === 0) return;
  
  const lastUndo = undoStack.pop();
  history.push(lastUndo);
  
  // 更新按钮状态
  foward_button.disabled = undoStack.length === 0;
  foward_button.style.cursor = undoStack.length === 0 ? 'not-allowed' : 'pointer';
  back_button.disabled = false;
  back_button.style.cursor = 'pointer';
  clear_button.disabled = false;
  clear_button.style.cursor = 'pointer';
}

// 更新画布绘制函数
function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // 绘制网格点
  circles.forEach((circle) => {
    ctx.beginPath();
    ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2);
    ctx.fillStyle = circle.color;
    ctx.fill();
  });
  // 遍历历史栈绘制所有操作
  history.forEach(op => {
    if (op.type === 'line') {
      const line = op.data;
      ctx.beginPath();
      ctx.lineWidth = 4;
      ctx.moveTo(line.start.x, line.start.y);
      ctx.lineTo(line.end.x, line.end.y);
      ctx.stroke();
    } else if (op.type === 'image') {
      const data = op.data;
      ctx.drawImage(data.img, data.x, data.y, data.img.width, data.img.height);
    }
  });
  // 绘制临时线条
  if (startCircle && endCircle) {
    ctx.beginPath();
    ctx.lineWidth = 4;
    ctx.moveTo(startCircle.x, startCircle.y);
    ctx.lineTo(endCircle.x, endCircle.y);
    ctx.stroke();
  }
  
  requestAnimationFrame(draw);
  // 更新清空按钮状态
  clear_button.disabled = history.length === 0;
  clear_button.style.cursor = history.length === 0 ? 'not-allowed' : 'pointer';
}

// 更新清空画布函数
function clearCanvas() {
  if (confirm('Are you sure you want to delete the circuit?')) {
    history.length = 0;
    undoStack.length = 0;
    clear_button.disabled = true;
    clear_button.style.cursor = 'not-allowed';
    foward_button.disabled = true;
    foward_button.style.cursor = 'not-allowed';
    back_button.disabled = true;
    back_button.style.cursor = 'not-allowed';
  }
}

说明:

  • 所有操作按执行顺序存入history栈,撤销时直接取栈顶元素,完全符合"后进先出"的撤销逻辑
  • 被撤销的操作存入undoStack,重做时再放回history恢复操作
  • 画布绘制时遍历历史栈,按顺序重绘所有操作,确保画布状态与操作历史完全一致

内容的提问来源于stack exchange,提问作者Skerdi Velo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 22:45:46