canvas水印是将文字或图片直接绘制到像素层,导出后不可分离;必须确保img.onload完成、设置crossorigin、正确使用textbaseline/translate/rotate、适配dpr并规范todataurl参数。

Canvas 水印不是“加一层遮罩”,而是把文字或图片真正画进像素——导出后不可分离,但必须手动控制加载、坐标、透明度和导出参数,否则白忙一场。
canvas.drawImage 必须等 img.onload 完成才能调用
直接 img.src = "xxx.jpg" 后立刻 ctx.drawImage(img, 0, 0),画布会是空的。浏览器不会报错,但什么也画不出来。
- 图片必须监听
onload事件,且该事件只在图片完全解码后触发 - 跨域图片(如 CDN 上的图)需额外加
img.crossOrigin = "anonymous",否则后续toDataURL()会抛SecurityError - 用
URL.createObjectURL(file)读取本地文件时,不用设crossOrigin,但记得上传后及时revokeObjectURL防内存泄漏
fillText 坐标和对齐容易偏移,textBaseline 和 textAlign 得配着用
ctx.fillText("水印", x, y) 的 y 是文字基线位置,不是顶部。默认 textBaseline = "alphabetic",导致文字“悬空”;不设 textAlign 时,x 对应左边缘,极易错位。
- 居中显示推荐:
ctx.textBaseline = "middle"+ctx.textAlign = "center" - 字体必须显式声明大小和字体族,例如
ctx.font = "bold 16px sans-serif",否则可能回退到极小的默认值(如 10px) - 半透明建议用
rgba(255, 255, 255, 0.25),别用ctx.globalAlpha = 0.25后再填色——后者会影响后续所有绘制
斜向平铺水印要用 save/restore + translate + rotate,不能只 rotate 一次
只写 ctx.rotate(Math.PI / 9),整个 canvas 坐标系就歪了,后面所有 fillText 或 drawImage 全部错位,而且无法恢复。
- 每次旋转前必须
ctx.save(),绘制完立即ctx.restore() - 旋转中心点靠
ctx.translate(x, y)移过去,再rotate,否则绕的是画布原点(0, 0) - 平铺效果要循环计算多个位置,例如每 120px 放一个,角度统一为 20°,避免单行水印被裁剪
- 高 DPR 屏幕(如 Retina)下,记得用
window.devicePixelRatio缩放 canvas.width/height,并相应调整ctx.scale(dpr, dpr)
toDataURL 导出时 MIME 类型、质量、跨域三者必须同时对齐
canvas.toDataURL() 看似简单,但参数错一个,导出就失效:PNG 体积大但无损,JPEG 体积小但模糊,跨域图不处理直接崩。
- 导出 JPEG 必须传两个参数:
toDataURL("image/jpeg", 0.85);只传类型不传质量,某些安卓 WebView 会忽略质量并返回 PNG - 跨域图未设
crossOrigin="anonymous"就调toDataURL,会抛DOMException: The canvas has been tainted by cross-origin data - 大图(宽/高 > 4096px)在 Safari iOS 上可能截断,建议预缩放至安全尺寸再绘制
- 导出后若要替换原
<img>,直接赋值img.src = dataUrl即可,无需 base64 解码
最常被跳过的其实是 DPR 适配和 save/restore 配合旋转——前者让水印在 iPhone 上发虚,后者让整页布局突然错乱。这两处没处理,水印就算加上了,也不算“可用”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











