前段时间作某系统审核后台,出现了审核人员截图把内容外泄露的状况,虽然截图内容不是特别敏感,可是安全问题仍是不能忽视。因而便在系统页面上面加上了水印,对于审核人员截图等敏感操做有必定的提示做用。javascript
Canvas兼容性html
这里咱们用canvas来生成base64图片,经过CanIUse网站查询兼容性,若是在移动端以及一些管理系统使用,兼容性问题能够彻底忽略。前端
HTMLCanvasElement.toDataURL
方法返回一个包含图片展现的 data URI 。可使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi。java
若是画布的高度或宽度是0,那么会返回字符串“data:,”。
若是传入的类型非“image/png”,可是返回的值以“data:image/png”开头,那么该传入的类型是不支持的。
Chrome支持“image/webp”类型。具体参考HTMLCanvasElement.toDataURLnode
具体代码实现以下:git
(function () {
// canvas 实现 watermark
function __canvasWM({
// 使用 ES6 的函数默认值方式设置参数的默认取值
// 具体参见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Default_parameters
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 args = arguments[0];
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();
const 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;
})();
// 调用
__canvasWM({
content: 'QQMusicFE'
})
复制代码
效果以下:
![Canvas实现网页水印效果]github
为了使这个方法更通用,兼容不一样的引用方式,咱们还能够加上这段代码:web
// 为了兼容不一样的环境
if (typeof module != 'undefined' && module.exports) { //CMD
module.exports = __canvasWM;
} else if (typeof define == 'function' && define.amd) { // AMD
define(function () {
return __canvasWM;
});
} else {
window.__canvasWM = __canvasWM;
}
复制代码
这样彷佛能知足咱们的需求了,可是还有一个问题,稍微懂一点浏览器的使用或者网页知识的用户,能够用浏览器的开发者工具来动态更改DOM的属性或者结构就能够去掉了。这个时候有两个解决办法:canvas
MutationObserver给开发者们提供了一种能在某个范围内的DOM树发生变化时做出适当反应的能力。数组
MutationObserver兼容性
经过兼容性表能够看出高级浏览器以及移动浏览器支持很是不错。
Mutation Observer API 用来监视 DOM 变更。DOM 的任何变更,好比节点的增减、属性的变更、文本内容的变更,这个 API 均可以获得通知。
使用MutationObserver构造函数,新建一个观察器实例,实例的有一个回调函数,该回调函数接受两个参数,第一个是变更数组,第二个是观察器实例。MutationObserver 的实例的observe方法用来启动监听,它接受两个参数。
第一个参数:所要观察的 DOM 节点,第二个参数:一个配置对象,指定所要观察的特定变更,有如下几种:
属性 | 描述 |
---|---|
childList | 若是须要观察目标节点的子节点(新增了某个子节点,或者移除了某个子节点),则设置为true. |
attributes | 若是须要观察目标节点的属性节点(新增或删除了某个属性,以及某个属性的属性值发生了变化),则设置为true. |
characterData | 若是目标节点为characterData节点(一种抽象接口,具体能够为文本节点,注释节点,以及处理指令节点)时,也要观察该节点的文本内容是否发生变化,则设置为true. |
subtree | 除了目标节点,若是还须要观察目标节点的全部后代节点(观察目标节点所包含的整棵DOM树上的上述三种节点变化),则设置为true. |
attributeOldValue | 在attributes属性已经设为true的前提下,若是须要将发生变化的属性节点以前的属性值记录下来(记录到下面MutationRecord对象的oldValue属性中),则设置为true. |
characterDataOldValue | 在characterData属性已经设为true的前提下,若是须要将发生变化的characterData节点以前的文本内容记录下来(记录到下面MutationRecord对象的oldValue属性中),则设置为true. |
attributeFilter | 一个属性名数组(不须要指定命名空间),只有该数组中包含的属性名发生变化时才会被观察到,其余名称的属性发生变化后会被忽略. |
MutationObserver
只能监测到诸如属性改变、增删子结点等,对于本身自己被删除,是没有办法的能够经过监测父结点来达到要求。所以最终改造以后代码为:
(function () {
// canvas 实现 watermark
function __canvasWM({
// 使用 ES6 的函数默认值方式设置参数的默认取值
// 具体参见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Default_parameters
container = document.body,
width = '300px',
height = '200px',
textAlign = 'center',
textBaseline = 'middle',
font = "20px Microsoft Yahei",
fillStyle = 'rgba(184, 184, 184, 0.6)',
content = '请勿外传',
rotate = '30',
zIndex = 1000
} = {}) {
const args = arguments[0];
const canvas = document.createElement('canvas');
canvas.setAttribute('width', width);
canvas.setAttribute('height', height);
const 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);
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元素变更才从新调用 __canvasWM
if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) {
// 避免一直触发
mo.disconnect();
mo = null;
__canvasWM(JSON.parse(JSON.stringify(args)));
}
});
mo.observe(container, {
attributes: true,
subtree: true,
childList: true
})
}
}
if (typeof module != 'undefined' && module.exports) { //CMD
module.exports = __canvasWM;
} else if (typeof define == 'function' && define.amd) { // AMD
define(function () {
return __canvasWM;
});
} else {
window.__canvasWM = __canvasWM;
}
})();
// 调用
__canvasWM({
content: 'QQMusicFE'
});
复制代码
SVG:可缩放矢量图形(英语:Scalable Vector Graphics,SVG)是一种基于可扩展标记语言(XML),用于描述二维矢量图形的图形格式。 SVG由W3C制定,是一个开放标准。 -- 维基百科
SVG浏览器兼容性
相比Canvas,SVG有更好的浏览器兼容性,使用SVG生成水印的方式与Canvas的方式相似,只是base64Url的生成方式换成了SVG。具体以下:
(function () {
// svg 实现 watermark
function __svgWM({
container = document.body,
content = '请勿外传',
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");
// ...
// 与 canvas 的一致
// ...
})();
__svgWM({
content: 'QQMusicFE'
})
复制代码
身为现代前端开发者,Node.JS也是须要掌握的。咱们一样能够经过NodeJS来生成网页水印(出于性能考虑更好的方式是利用用户客户端来生成)。前端发一个请求,参数带上水印内容,后台返回图片内容。
具体实现(Koa2环境):
ctx.type = 'image/png';
设置响应为图片类型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`)))
}
});
})
})());
});
复制代码
若是只是简单的水印展现,建议在浏览器生成,性能更好
除了给网页加上水印以外,有时候咱们须要给图片也加上水印,这样在用户保存图片后,带上了水印来源信息,既能够保护版权,水印的其余信息也能够防止泄密。
实现以下:
(function() {
function __picWM({
url = '',
textAlign = 'center',
textBaseline = 'middle',
font = "20px Microsoft Yahei",
fillStyle = 'rgba(184, 184, 184, 0.8)',
content = '请勿外传',
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);
}
}
if (typeof module != 'undefined' && module.exports) { //CMD
module.exports = __picWM;
} else if (typeof define == 'function' && define.amd) { // AMD
define(function () {
return __picWM;
});
} else {
window.__picWM = __picWM;
}
})();
// 调用
__picWM({
url: 'http://localhost:3000/imgs/google.png',
content: 'QQMusicFE',
cb: (base64Url) => {
document.querySelector('img').src = base64Url
},
});
复制代码
效果以下:
Canvas给图片生成水印
咱们一样能够经过gm这个库来给图片加上水印
function picWM(path, text) {
imageMagick(path)
.drawText(10, 50, text)
.write(require('path').join(__dirname, `./${text}.png`), function (err) {
if (err) {
console.log(err);
}
});
}
复制代码
若是须要批处理图片,只须要遍历相关文件便可。
若是只是简单的水印展现,建议在浏览器生成,性能更好
前段时间阿里凭截图查到了月饼事件的泄密者,其实就是用了隐水印。这其实很大程度不是前端的范畴了,可是咱们也应该了解。AlloyTeam团队写过一篇 不能说的秘密——前端也能玩的图片隐写术 ,经过Canvas给图片加上了“隐水印”,针对用户保存的图片,是能够轻松还原里面隐含的内容,可是对于截图或者处理过的照片却无能为力,不过对于一些机密图片文件展现,是能够偷偷用上该技术的。
前端生成的水印也能够,别人也能够用一样的方式生成,可能会有“嫁祸于人”(可能这是多虑的),咱们仍是要有更安全的解决方法。水印内容能够包含多种编码后的信息,包括用户名、用户ID、时间等。好比咱们只是想保存用户惟一的用户ID,须要把用户ID传入下面的md5方法,就能够生成惟一标识。编码后的信息是不可逆的,但能够经过全局遍历全部用户的方式进行追溯。这样就能够防止水印造假也能够追溯真正水印的信息。
// MD5加密库 utility
const utils = require('utility')
// 加盐MD5
exports.md5 = function (content) {
const salt = 'microzz_asd!@#IdSDAS~~';
return utils.md5(utils.md5(content + salt));
}
复制代码
安全问题不能大意,对于一些比较敏感的内容,咱们能够经过组合使用上述的水印方案,这样才能最大程度给浏览者警示的做用,减小泄密的状况,即便泄密了,也有可能追踪到泄密者。
lucifer-基于KM水印的图片网页水印实现方案
damon-网页水印明水印前端SVG实现方案