
用闭包封装带撤销功能的画板,核心是把画布状态(如路径、颜色、尺寸)快照保存在私有数组中,并通过闭包保护这些状态不被外部直接修改。下面分三步实现关键逻辑:
1. 用闭包维护历史栈和当前状态
闭包提供私有作用域,存放 history 数组、currentIndex(指向当前可操作状态)、以及画布上下文等。外部只能通过暴露的方法操作,无法篡改历史数据。
示例结构:
function createSketchpad(canvas) {
const ctx = canvas.getContext('2d');
const history = [];
let currentIndex = -1;
let isDrawing = false;
let lastX = 0, lastY = 0;
<p>// 保存当前画布为快照(转为 dataURL 或 getImageData)
function saveState() {
history.splice(currentIndex + 1); // 清除“重做”分支
history.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
currentIndex++;
}</p><p>// 恢复指定索引的状态
function restoreState(index) {
if (index = history.length) return;
ctx.putImageData(history[index], 0, 0);
currentIndex = index;
}</p><p>// 其他方法(如 clear、undo、redo)在此定义并返回
return {
startDraw(x, y) { /<em> ... </em>/ },
drawTo(x, y) { /<em> ... </em>/ },
endDraw() { saveState(); },
undo() { if (currentIndex > 0) restoreState(--currentIndex); },
redo() { if (currentIndex </p>2. 优化性能:避免频繁 getImageData 复制大图
每次绘制都调用 getImageData 开销大,尤其高分辨率画布。更实用的做法是只记录“绘图指令”(命令模式),而非像素快照。
- 每步操作存为轻量对象:如
{ type: 'line', from: [x1,y1], to: [x2,y2], color: '#000', lineWidth: 2 } -
saveState()只 push 当前指令;restoreState()重放从开头到目标索引的所有指令 - 支持增量撤销:用户画一条线 → 存一条指令;再画一段弧 → 再存一条;撤销就删最后一条,重放剩余全部
3. 绑定事件并初始化交互
在闭包外绑定鼠标/触摸事件,调用闭包内方法。注意防抖保存、处理坐标缩放、适配移动端 touch 事件。
关键点:
- 监听
mousedown/touchstart→ 调用startDraw(x, y) - 监听
mousemove/touchmove→ 调用drawTo(x, y)(实时绘制) - 监听
mouseup/touchend→ 调用endDraw()(触发保存) - 按钮点击调用
undo()或redo(),禁用按钮时根据currentIndex状态控制
这样封装后,画板逻辑完全隔离,history 不可外部访问,API 简洁可控,也便于单元测试和后续扩展(比如加导出、图层、笔刷类型)。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











