探究 canvas 绘图中撤销(undo)功能的实现方式详解

2019-01-28 21:00:56于丽
putImageData 这两个方法会产生比较严重的性能问题。stackoverflow 上有详细的讨论: Why is putImageData so slow? 。我们还可以从 jsperf 上这个测试用例的数据来验证这一点。淘宝 FED 在Canvas 最佳实践中也提到了尽量“不在动画中使用 putImageData 方法”。另外,文章里还提到一点,“尽可能调用那些渲染开销较低的 API”。我们可以从这里入手思考如何进行优化。

之前说过,我们通过对整个画布保存快照的方式来记录每个操作,换个角度思考,如果我们把每次绘制的动作保存到一个数组中,在每次执行撤销操作时,首先清空画布,然后重绘这个绘图动作数组,也可以实现撤销操作的功能。可行性方面,首先这样可以减少保存到内存的数据量,其次还避免了使用渲染开销较高的 putImageData 。以 drawImage 为比较对象,看 jsperf 上这个测试用例,二者的性能存在数量级的差距。

因此,我们认为此优化方案是可行的。

改进后的应用方式大致如下:

class WrappedCanvas { constructor (canvas) { this.ctx = canvas.getContext('2d'); this.width = this.ctx.canvas.width; this.height = this.ctx.canvas.height; this.executionArray = []; } drawImage (...params) { this.executionArray.push({ method: 'drawImage', params: params }); this.ctx.drawImage(...params); } clearCanvas () { this.ctx.clearRect(0, 0, this.width, this.height); } undo () { if (this.executionArray.length > 0) { // 清空画布 this.clearCanvas(); // 删除当前操作 this.executionArray.pop(); // 逐个执行绘图动作进行重绘 for (let exe of this.executionArray) { this[exe.method](...exe.params) } } } }

新人入坑 canvas,如有错误与不足,欢迎指出。以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持易采站长站。