先看下效果:
思路1:
使用canvas
進(jìn)行生成圖片,然后動(dòng)態(tài)生成div
填充整個(gè)背景,將生成的圖片用與 background-image
屬性上,進(jìn)行頁面填充.
問題:
如果在開發(fā)者工具里取消了這個(gè)屬性,則該水印效果就會(huì)消失,所以需要一種方法監(jiān)視該div,不讓其修改,或修改了之后又進(jìn)行恢復(fù),此處就用到了用來監(jiān)視 DOM
變動(dòng)的Mutation Observer
API .DOM 的任何變動(dòng),比如節(jié)點(diǎn)的增減柳刮、屬性的變動(dòng)挖垛、文本內(nèi)容的變動(dòng),這個(gè) API 都可以得到通知秉颗。參考MDN:MutationObserver
MutationObserver
只能監(jiān)測到諸如屬性改變痢毒、增刪子結(jié)點(diǎn)等,對(duì)于自己本身被刪除蚕甥,是沒有辦法的,那么可以通過監(jiān)測父結(jié)點(diǎn)來達(dá)到要求哪替。
代碼:
/**
* 網(wǎng)頁加水印
*
* @export
* @param {*} [{
* container = document.body,
* width = '400px',
* height = '300px',
* textAlign = 'center',
* textBaseline = 'middle',
* font = "20px Microsoft Yahei",
* fillStyle = 'rgba(230, 230, 230, 0.8)',
* content = '保密水印',
* rotate = '10',
* zIndex = -1000
* }={}]
* @returns
*/
export function __waterDocument({
container = document.body,
width = '200px',
height = '150px',
textAlign = 'center',
textBaseline = 'middle',
font = "20px Microsoft Yahei",
fillStyle = 'rgba(230, 230, 230, 0.8)',
content = '保密水印',
rotate = '10',
zIndex = -1000
} = {}) {
const args = arguments[0];
const canvas = document.createElement('canvas');
canvas.setAttribute('width', width);
canvas.setAttribute('height', height);
const ctx = canvas.getContext("2d");
if (ctx === null) {
console.error("this browser is not support canvas.");
return;
}
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);
const base64Url = canvas.toDataURL();
const __wm = document.querySelector('.__wm');
const watermarkDiv = __wm || document.createElement("div");
const styleStr = `
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
z-index:${zIndex};
pointer-events:none;
background-repeat:repeat;
background-image:url('${base64Url}')`;
watermarkDiv.setAttribute('style', styleStr);
watermarkDiv.classList.add('__wm');
if (!__wm) {
container.style.position = 'relative';
container.insertBefore(watermarkDiv, container.firstChild);
}
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
if (MutationObserver) {
let mo = new MutationObserver(function () {
const __wm = document.querySelector('.__wm');
// 只在__wm元素變動(dòng)才重新調(diào)用 __canvasWM
if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) {
// 避免一直觸發(fā)
mo.disconnect();
mo = null;
__canvasWM(JSON.parse(JSON.stringify(args)));
}
});
mo.observe(container, {
attributes: true,
subtree: true,
childList: true
})
}
}
思路2:
如果瀏覽器對(duì)canvas
不支持則可以使用svg
,svg瀏覽器支持面比canvas要大一些
可以使用https://caniuse.com/進(jìn)行查看
實(shí)現(xiàn)原理與canvas幾乎一致,只是圖片替換成了svg
/**
*網(wǎng)頁加水印 svg 方式
*
* @export
* @param {*} [{
* container = document.body,
* content = '請(qǐng)勿外傳',
* width = '300px',
* height = '200px',
* opacity = '0.2',
* fontSize = '20px',
* zIndex = 1000
* }={}]
*/
export function __waterDocumentSvg({
container = document.body,
content = '請(qǐng)勿外傳',
width = '300px',
height = '200px',
opacity = '0.2',
fontSize = '20px',
zIndex = 1000
} = {}) {
const args = arguments[0];
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");
const styleStr = `
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
z-index:${zIndex};
pointer-events:none;
background-repeat:repeat;
background-image:url('${base64Url}')`;
watermarkDiv.setAttribute('style', styleStr);
watermarkDiv.classList.add('__wm');
if (!__wm) {
container.style.position = 'relative';
container.insertBefore(watermarkDiv, container.firstChild);
}
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
if (MutationObserver) {
let mo = new MutationObserver(function () {
const __wm = document.querySelector('.__wm');
// 只在__wm元素變動(dòng)才重新調(diào)用 __canvasWM
if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) {
// 避免一直觸發(fā)
mo.disconnect();
mo = null;
__waterDocumentSvg(JSON.parse(JSON.stringify(args)));
}
});
mo.observe(container, {
attributes: true,
subtree: true,
childList: true
})
}
}
通過NodeJS生成水印
身為現(xiàn)代前端開發(fā)者,Node.JS也是需要掌握的菇怀。我們同樣可以通過NodeJS來生成網(wǎng)頁水印,前端發(fā)一個(gè)請(qǐng)求凭舶,參數(shù)帶上水印內(nèi)容,后臺(tái)返回圖片內(nèi)容爱沟。
具體實(shí)現(xiàn)(Koa2環(huán)境):
- 安裝gm以及相關(guān)環(huán)境帅霜,詳情看gm文檔
-
ctx.type = 'image/png';
設(shè)置響應(yīng)為圖片類型 - 生成圖片過程是異步的,所以需要包裝一層Promise呼伸,這樣才能為通過 async/await 方式為 ctx.body 賦值
const fs = require('fs')
const gm = require('gm');
const imageMagick = gm.subClass({
imageMagick: true
});
const router = require('koa-router')();
router.get('/wm', async (ctx, next) => {
const {
text
} = ctx.query;
ctx.type = 'image/png';
ctx.status = 200;
ctx.body = await ((() => {
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`), function (err) {
if (err) {
reject(err);
} else {
resolve(fs.readFileSync(require('path').join(__dirname, `./${text}.png`)))
}
});
})
})());
});
圖片水印生成解決方案
通過canvas給圖片加水印
思路:讀取圖片,在圖片加載完之后用canvas填充圖片,并填充加密文字,然后將圖片轉(zhuǎn)成base64url傳值給回調(diào)函數(shù)
/**
*圖片加水印
*
* @export
* @param {*} [{
* url = '',
* textAlign = 'center',
* textBaseline = 'middle',
* font = "20px Microsoft Yahei",
* fillStyle = 'rgba(184, 184, 184, 0.8)',
* content = '請(qǐng)勿外傳',
* cb = null,
* textX = 100,
* textY = 30
* }={}]
*/
export function __picWM({
url = '',
textAlign = 'center',
textBaseline = 'middle',
font = "20px Microsoft Yahei",
fillStyle = 'rgba(184, 184, 184, 0.8)',
content = '請(qǐng)勿外傳',
cb = null,
textX = 100,
textY = 30
} = {}) {
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();
cb && cb(base64Url);
}
}
調(diào)用:
__picWM({
url: 'https://www.baidu.com/img/bd_logo1.png?where=super',
content: '加密圖片',
cb: (base64Url) => {
document.getElementById('s_lg_img').src = base64Url
},
})
效果:
使用加密后的水印內(nèi)容
前端生成的水印也可以身冀,別人也可以用同樣的方式生成,可能會(huì)有“嫁禍于人”(可能這是多慮的),我們還是要有更安全的解決方法搂根。水印內(nèi)容可以包含多種編碼后的信息珍促,包括用戶名、用戶ID兄墅、時(shí)間等踢星。比如我們只是想保存用戶唯一的用戶ID,需要把用戶ID傳入下面的md5方法隙咸,就可以生成唯一標(biāo)識(shí)沐悦。編碼后的信息是不可逆的,但可以通過全局遍歷所有用戶的方式進(jìn)行追溯五督。這樣就可以防止水印造假也可以追溯真正水印的信息藏否。
// MD5加密庫 utility
const utils = require('utility')
// 加鹽MD5
exports.md5 = function (content) {
const salt = 'microzz_asd!@#IdSDAS~~';
return utils.md5(utils.md5(content + salt));
}
參考:前端水印生成方案 --QQ音樂前端團(tuán)隊(duì)