canvas是唯一能将水印“焊进图片”的方式,因其通过drawimage()与toblob()将水印写入像素生成新图像数据,而css伪元素、背景图等方案仅属视觉叠加,右键保存或禁用样式后水印即消失。

canvas 是唯一能真正把水印“焊进图片”的方式,其他所有 CSS、伪元素、背景图方案,用户右键另存为、截图、禁用样式后水印立刻消失。
为什么不能直接在 <img> 上加水印
浏览器的 <img> 元素是只读渲染节点,不支持绘制、叠加或像素操作。你看到的所有“给图片加水印”的 CSS 方案,其实都是在图片**上方或下方**叠了一层文字/图形——它和原图完全分离,导出、截图、打印时根本不会包含进去。
- 常见错误:对
<img>写::after伪元素,或用background-image覆盖 —— 这只是视觉欺骗,不是真水印 - 后果:用户打开开发者工具删掉那个伪元素,水印就没了;右键保存图片,拿到的是干净原图
- 本质区别:
canvas绘制后调用toDataURL()或toBlob(),生成的是**新图像数据**,水印已写入像素
canvas.drawImage() 后必须等 img.onload
直接 ctx.drawImage(img, 0, 0) 而没监听加载完成,大概率白屏或静默失败——因为图片还没解码完,drawImage 不报错但什么也不画。
- 必须显式绑定:
img.onload = () => { ctx.drawImage(img, 0, 0); /* 后续水印 */ }; - 如果图片来自 CDN 或跨域资源,还得加
img.crossOrigin = "anonymous",否则后续toDataURL()会抛SecurityError - 服务端必须返回
Access-Control-Allow-Origin: *(或指定域名),否则前端设了crossOrigin也无效
文字水印位置和字体容易踩坑
fillText() 的 y 参数不是文字顶部,而是**基线(baseline)位置**。设成 y=20 却看不到字?大概率被画到 canvas 顶边外了。
- 务必设置:
ctx.textBaseline = "middle";和ctx.textAlign = "center";,再用fillText(text, x, y)才好控制居中 - 字体必须显式声明大小和族:
ctx.font = "bold 48px sans-serif",不设会回退到极小默认值(如 10px),高 DPR 屏上糊成一片 - 颜色推荐
rgba(0, 0, 0, 0.12)—— 太透明看不见,太浓(比如0.5)直接遮挡主体内容
导出时选 toBlob() 而不是 toDataURL()
大图(比如 4000×3000)用 toDataURL() 会卡死主线程,生成的 base64 字符串可能长达数 MB,内存暴涨且无法流式处理。
- 优先用:
canvas.toBlob(callback, "image/jpeg", 0.9),回调里拿Blob对象,可直接上传、创建 URL 或下载 - JPEG 质量参数别低于
0.7,否则水印边缘锯齿严重,尤其斜向文字 - 如果要保留透明通道(比如 PNG 水印图带 alpha),必须用
"image/png",JPEG 不支持透明
真正防不住截图,但能拦住 90% 的右键另存为和批量盗图。关键不是“绝对安全”,而是让复制成本明显高于获取意愿——而这个分界点,就在是否调用了 drawImage() + toBlob() 这两步。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











