Canvas中goBack()撤销函数执行顺序异常问题求助
修复撤销操作的顺序问题
问题出在你把图片和线条的操作分别存在两个独立数组中,撤销时优先处理图片数组,完全忽略了操作的时间顺序。正确的做法是用统一的历史栈记录所有操作,按执行顺序存储,撤销时直接从栈顶取最后一次操作处理。
修改步骤:
替换独立数组为统一历史栈
删除原有的drawnImageData、lines、deletedImageData、deletedLines数组,改用两个栈:history:按时间顺序记录所有已执行的操作undoStack:记录被撤销的操作,用于重做功能
修改操作添加逻辑
- 拖放图片时,向
history添加类型为image的操作对象 - 绘制线条时,向
history添加类型为line的操作对象
- 拖放图片时,向
重写撤销/重做函数
goBack():弹出history最后一个操作,根据类型处理后放入undoStackgoFoward():弹出undoStack最后一个操作,放回history恢复操作
更新画布绘制逻辑
遍历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
相关产品推荐
相关产品推荐

