Create Unbreakable Web Page Watermarks with Canvas, SVG, and Node.js
This article presents multiple techniques for generating robust web page and image watermarks—including Canvas and SVG rendering, MutationObserver protection, and Node.js server-side generation—complete with code examples, compatibility notes, and best‑practice recommendations to prevent content leakage and enable traceability.
前段时间做某系统审核后台,出现了审核人员截图把内容外泄露的情况,虽然截图内容不是特别敏感,但是安全问题还是不能忽视。于是便在系统页面上面加上了水印,对于审核人员截图等敏感操作有一定的提示作用。
Web Watermark Generation Solution
Generate watermark via Canvas
We use canvas to create a base64 image; compatible with mobile and admin systems. HTMLCanvasElement.toDataURL returns a data URI that can be used as an image source.
(function () {
// canvas watermark
function __canvasWM({
container = document.body,
width = '200px',
height = '150px',
textAlign = 'center',
textBaseline = 'middle',
font = "20px microsoft yahei",
fillStyle = 'rgba(184, 184, 184, 0.8)',
content = '请勿外传',
rotate = '30',
zIndex = 1000
} = {}) {
var canvas = document.createElement('canvas');
canvas.setAttribute('width', width);
canvas.setAttribute('height', height);
var ctx = canvas.getContext("2d");
ctx.textAlign = textAlign;
ctx.textBaseline = textBaseline;
ctx.font = font;
ctx.fillStyle = fillStyle;
ctx.rotate(Math.PI / 180 * rotate);
ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2);
var base64Url = canvas.toDataURL();
var watermarkDiv = document.createElement("div");
watermarkDiv.setAttribute('style', `
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
z-index:${zIndex};
pointer-events:none;
background-repeat:repeat;
background-image:url('${base64Url}')`);
container.style.position = 'relative';
container.insertBefore(watermarkDiv, container.firstChild);
window.__canvasWM = __canvasWM;
}
// usage
__canvasWM({content: 'QQMusicFE'});
})();Resulting watermark is tiled across the page.
Make watermark robust with MutationObserver
Use MutationObserver to monitor DOM changes and recreate the watermark if it is removed.
(function () {
// canvas watermark implementation (same as above)
// ...
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
if (MutationObserver) {
let mo = new MutationObserver(function () {
const __wm = document.querySelector('.__wm');
if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) {
mo.disconnect();
__canvasWM(JSON.parse(JSON.stringify(args)));
}
});
mo.observe(container, {attributes:true, subtree:true, childList:true});
}
window.__canvasWM = __canvasWM;
})();MutationObserver API can watch attributes, childList, characterData, subtree, attributeOldValue, characterDataOldValue, and attributeFilter.
Generate watermark via SVG
SVG provides better compatibility; generate a base64 URL from an SVG string.
(function () {
function __svgWM({
container = document.body,
content = '请勿外传',
width = '300px',
height = '200px',
opacity = '0.2',
fontSize = '20px',
zIndex = 1000
} = {}) {
const svgStr = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${width}">
<text x="50%" y="50%" dy="12px" text-anchor="middle" stroke="#000000"
stroke-width="1" stroke-opacity="${opacity}" fill="none"
transform="rotate(-45, 120 120)" style="font-size: ${fontSize};">
${content}
</text>
</svg>`;
const base64Url = `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgStr)))}`;
const __wm = document.querySelector('.__wm');
const watermarkDiv = __wm || document.createElement("div");
// apply similar style as canvas version using base64Url
// ...
}
__svgWM({content: 'QQMusicFE'});
})();Generate watermark via Node.js (Koa2)
Server can generate PNG watermark images using gm/ImageMagick.
const fs = require('fs');
const gm = require('gm');
const imageMagick = gm.subClass({imageMagick:true});
const router = require('koa-router')();
router.get('/wm', async (ctx) => {
const {text} = ctx.query;
ctx.type = 'image/png';
ctx.status = 200;
ctx.body = await (async () => {
return new Promise((resolve, reject) => {
imageMagick(200,100,"rgba(255,255,255,0)")
.fontSize(40)
.drawText(10,50,text)
.write(require('path').join(__dirname, `./${text}.png`), (err) => {
if (err) reject(err);
else resolve(fs.readFileSync(require('path').join(__dirname, `./${text}.png`)));
});
});
})();
});
module.exports = router;For simple watermarks, client‑side generation is more performant.
Image Watermark Generation
Canvas image watermark
(function () {
function __picWM({
url = '',
textAlign = 'center',
textBaseline = 'middle',
font = "20px Microsoft Yahei",
fillStyle = 'rgba(184, 184, 184, 0.8)',
content = '请勿外传',
textX = 100,
textY = 30,
cb = null
} = {}) {
const img = new Image();
img.src = url;
img.crossOrigin = 'anonymous';
img.onload = function () {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img,0,0);
ctx.textAlign = textAlign;
ctx.textBaseline = textBaseline;
ctx.font = font;
ctx.fillStyle = fillStyle;
ctx.fillText(content, img.width - textX, img.height - textY);
const base64Url = canvas.toDataURL();
if (cb) cb(base64Url);
};
}
// usage example
__picWM({
url: 'http://localhost:3000/imgs/google.png',
content: 'QQMusicFE',
cb: (base64Url) => { document.querySelector('img').src = base64Url; }
});
})();Node.js batch image watermark
function picWM(path, text) {
imageMagick(path)
.drawText(10,50,text)
.write(require('path').join(__dirname, `./${text}.png`), (err) => {
if (err) console.log(err);
});
}Iterate over files to apply batch processing.
Advanced Topics
Invisible Watermark
Using steganography via Canvas to embed hidden data that can be recovered from saved images but not from screenshots.
Encrypted Watermark Content
Encode user‑specific information (e.g., user ID) with MD5 and embed it as watermark text.
// MD5 utility
const utils = require('utility');
exports.md5 = function (content) {
const salt = 'microzz_asd!@#IdSDAS~~';
return utils.md5(utils.md5(content + salt));
};Conclusion
Combining the above methods provides strong visual cues and traceability for sensitive content, reducing the risk of data leakage.
References
Cannot Speak Secret – Front‑end Image Steganography
阮一峰 – Mutation Observer API
lucifer – KM watermark image solution
damon – Front‑end SVG watermark solution
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
