最近在做网页版图片处理相关的项目,也算是初入了 canvas 的坑。项目需求中有一个给图片添加水印的功能。我们知道,在浏览器端实现图片添加水印功能,通常的做法就是使用 canvas 的 drawImage 方法。对于普通的合成(比如一张底图和一张 PNG 水印图片合成)来说,其大致实现原理如下:
| var canvas = document.getElementById("canvas"); var ctx = canvas.getContext('2d'); // img: 底图 // watermarkImg: 水印图片 // x, y 是画布上放置 img 的坐标 ctx.drawImage(img, x, y); ctx.drawImage(watermarkImg, x, y); |
直接连续使用 drawImage() 把对应的图片绘制到 canvas 画布上就行。
以上就是背景介绍。但是略麻烦的是添加水印的需求中还有一个需要实现的功能是用户能够切换水印的位置。我们自然会想到能否实现 canvas 的 undo 功能,当用户切换水印位置时,先撤销上一步 drawImage 操作,然后再重新绘制水印图片位置。
restore / save ?
效率最高也是最方便的肯定是查阅 canvas 2D 原生 API 是否有此功能。经过一番搜索, restore / save 这一对 API 进入视线。我们先看一下这两个 API 的描述:
CanvasRenderingContext2D.restore() 是 Canvas 2D API 通过在绘图状态栈中弹出顶端的状态,将 canvas 恢复到最近的保存状态的方法。 如果没有保存状态,此方法不做任何改变。
CanvasRenderingContext2D.save() 是 Canvas 2D API 通过将当前状态放入栈中,保存 canvas 全部状态的方法。
乍看起来可以满足需求。我们看一下官方示例代码:
| var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); ctx.save(); // 保存默认的状态 ctx.fillStyle = "green"; ctx.fillRect(10, 10, 100, 100); ctx.restore(); // 还原到上次保存的默认状态 ctx.fillRect(150, 75, 100, 100); |
结果如下图所示:

奇怪,好像和我们预期的结果不太一致。我们想要的结果是 save 方法调用后能够保存当前画布的快照, resolve 方法调用后能够完全回到上一个保存的快照处的状态。
再仔细研究一下 API。原来我们遗漏一个重要概念: drawing state ,也就是绘制状态。保存到栈中的绘制状态包含以下几个部分:
-
当前的变换矩阵
当前的剪切区域
当前的虚线列表
以下属性当前的值:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled.









