canvas撤销重做需记录路径指令而非截图,每条路径含起点、坐标点、线宽、颜色等属性;通过history和undone栈管理操作,撤销时移出history推入undone并重绘剩余路径,重做反之;重绘时直接设置ctx状态,注意移动端适配、节流、缩放导出等细节。

Canvas 本身不保存绘制历史,撤销重做必须靠代码主动记录和管理。核心思路是把每次完整落笔(mousedown 到 mouseup)画出的路径存为一条“指令”,而不是截图——这样省内存、重绘快、支持缩放导出。
用路径对象记录操作
每条路径包含起点、所有中间点、线宽、颜色、是否为橡皮擦等属性。鼠标按下时新建一个空路径对象;移动中用 lineTo() 追加坐标,并实时 stroke 显示;松开后把整个路径推入 history 数组。
- 避免用 getImageData 截图存历史,内存占用大且无法动态调整线宽/颜色
- 每条路径建议带 timestamp 或 id,方便调试和未来按时间过滤
- 移动端要同步监听 touchstart/touchmove/touchend,并调用 preventDefault() 阻止页面滚动
栈式管理 history 和 undone
history 存所有已完成操作,undone 存刚撤销但还能重做的操作。结构类似浏览器的前进后退。
- 点击撤销:从 history 弹出最后一项,push 到 undone,然后清空画布,再遍历剩余 history 逐条重放
- 点击重做:从 undone 弹出,push 回 history,同样清空再重绘全部
- history 为空时禁用撤销按钮,undone 为空时禁用重做按钮,用 disabled 控制 UI 状态
重绘时注意状态还原
重放路径时不要嵌套太多 save()/restore(),容易卡顿。直接用路径对象里的属性设置 context 状态即可:
- 先设 ctx.strokeStyle = path.color
- 再设 ctx.lineWidth = path.width
- 如果是橡皮擦,临时设 ctx.globalCompositeOperation = 'destination-out',画完记得切回 'source-over'
- 最后用 beginPath() → moveTo() → lineTo() × n → stroke() 重绘整条路径
几个容易忽略的细节
体验顺滑的关键不在画得多炫,而在响应及时、状态可控:
- mousemove 节流控制在 10–15ms,防止高频写点导致漏点或卡顿
- 鼠标松开后若还在画布上,可能意外触发新路径,需在 mouseup 里加判断是否仍在 canvas 区域内
- 初始化时务必同时设置 canvas.width / height 属性 和 CSS 宽高,否则缩放失真
- 导出图片前可临时放大 canvas(如 ×2),drawImage 缩放绘制后再恢复,提升清晰度
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











