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.

QQ Music Frontend Team
QQ Music Frontend Team
QQ Music Frontend Team
Create Unbreakable Web Page Watermarks with Canvas, SVG, and Node.js
前段时间做某系统审核后台,出现了审核人员截图把内容外泄露的情况,虽然截图内容不是特别敏感,但是安全问题还是不能忽视。于是便在系统页面上面加上了水印,对于审核人员截图等敏感操作有一定的提示作用。

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

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

MutationObserverwatermarknodejs
QQ Music Frontend Team
Written by

QQ Music Frontend Team

QQ Music Web Frontend Team

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.