为了防止信息泄露, 保障信息安全, 给网页添加水印是一种比较常见的方法.
本文介绍一种添加水印的方法, 具有以下特点:
不影响现有代码
可以任意给网页的不同部分添加水印
纯前端 JavaScript 实现
可简单防止用户通过浏览器开发者工具隐藏水印
思考一下, 生成的水印需要满足那些需求呢?
根据动态内容生成静态图片
包含一段标识信息, 同时需要覆盖足够的区域
根据水印的需求, 自然会想到用 background 指定 image , 并让其在 x,y 方向上重复展示, 来实现覆盖区域. 那么如何根据动态内容生成 image ? 你一定可以想到前端的绘图魔法 (Canvas / SVG). 首先绘制好我们需要的图形, 拿到图形编码后的 Data URLs 字符串, 该字符串包含所需的图像信息, 再配合使用 background 属性实现给网页添加水印.
绘图魔法 (一)-- Canvas
htmlCanvasElement.toDataURL 该方法返回一个包含图片信息的 Data URLs.
- const 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();
绘图魔法 (二)-- SVG
SVG: 可缩放矢量图形是一种基于可扩展标记语言, 用于描述二维矢量图形的图形格式. 使用 SVG 生成图片的方式和 Canvas 的方式类似, 只是 Data URLs 的生成方式换成了 SVG.
- const svgStr =
- `<svg xmlns="namespace" 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)))}`;
生成 Data URLs 之后, 可将其存为全局变量或者存在 store 中, 方便随时可取且保证生成一次. 通过阅读前文已经知道, 水印是可以通过在 DOM 节点上添加 background-image , background-repeat 属性来实现的. 那么如何给目标节点添加 background-image 属性呢? 来看看 vue,React 项目中如何实现的吧. 下文提到的 imageURI 为上文提到的 Data URLs.
Vue
1. 自定义指令, 在需要打上水印的节点上添加 v-watermarked 指令
- Vue.directive('watermarked', {
- bind(el, binding, vnode) {
- if (binding.value === undefined || !!binding.value) {
- el.style.backgroundImage = imageURI ;
- el.style.backgroundRepeat = 'space repeat';
- /*
- 顺手提一波
- background-repeat:space repeat;
- 图像在水平方向上尽可能重复, 但不会被裁剪, 第一个和最后一个图像会被固定在远足的相应的边上, 同时空白会均匀地分布在图像之间. 图像在垂直方向上重复来覆盖整个区域, 若大小不合适可被剪裁 */
- }
- },
- update(el, binding, vnode) {
- if (binding.value === undefined || !!binding.value) {
- el.style.backgroundImage = imageURI;
- el.style.backgroundRepeat = 'space repeat';
- }
- },
- });
2. 封装组件, 在需要打上水印的节点外层包上 <watermark>...</watermark>
- <template>
- <div
- class="watermark"
- :style="{
- backgroundRepeat: 'space repeat',
- backgroundImage:imageURI,
- }">
- <slot />
- </div>
- </template>
- react
1. 封装组件
- <Watermark>
- ...
- </Watermark>
- // 或者使用 render props
- <Watermark>
- {({blob}) => (
- ...
- )}
- </Watermark>
2. 直接写进组件样式里面
- // 以 styled-components 为例
- import styled from 'styled-components';
- const Root = styled.div`
- ${watermark()}
- `
3. 高阶组件
- @Watermark
- class Page extends React.Component {
- ...
- }
现在你已经大概了解如何给页面打上水印了, 你发现有什么问题了吗? 其实这存在一个弊端, 用户通过开发者工具动态更改 DOM 属性或者结构, 就可以轻松删除掉水印. 那么我们该如何阻止该行为呢?
MutationObserver
给开发者们提供了能在某个范围内的 DOM 树发生变化时作出适当反应的能力.
使用 MutationObserver 构造函数, 新建一个观察器实例, 实例的有一个回调函数, 该回调函数接受两个参数, 第一个是变动数组 (包含一系列变动记录 ), 第二个是观察器实例. MutationObserver 的实例的 observe 方法用来启动监听, 它接受两个参数. 第一个参数: 所要观察的 DOM 节点, 第二个参数: 一个配置对象, 指定所要观察的特定变动 ( config ).
MutationObserver 只能监测到诸如属性改变, 增删子节点等, 但需要注意的是对于自己本身被删除, 是没有办法的, 可以用过监测父节点来达到要求.
写个例子吧, 可以在监听到用户删除 style 属性操作, 及时恢复水印.
- const MutationObserver = Windows.MutationObserver || Windows.webKitMutationObserver;
- const config = {
- attributes: true, // 观察受监视元素的属性值更改
- attributeOldValue: true, // 记录任何有改动的属性的上一个值
- };
- const callback = (mutationList, observer) => {
- /*mutationList 包含一系列变动记录 */
- _.forEach(mutationList, (mutationRecord) => {
- const { type, attributeName } = mutationRecord;
- /*
- type 更改类型
- attributeName 返回被修改属性的属性名
- */
- if (type === 'attributes' && attributeName === 'style') {
- observer.disconnect(); // 先停止监听 否则会不断触发
- const { target, oldValue } = mutationRecord;
- /*
- target 为受监视元素
- oldValue 被更改属性值的旧值
- */
- target.setAttribute('style', oldValue);
- observer.observe(target, config); // 修改完后恢复监听
- }
- });
- };
- Vue.directive('watermarked', {
- bind(el, binding, vnode) {
- if (binding.value === undefined || !!binding.value) {
- el.style.backgroundImage = imageURI;
- el.style.backgroundRepeat = 'space repeat';
- const om = new MutationObserver(callback);
- om.observe(el, config);
- }
- },
- });
这里提供两个 demo, 以供参考.
Vue 的 demo https://github.com/chensener/water-mark
React 的 demo https://github.com/chensener/water-mark-react
来源: http://www.tuicool.com/articles/qE3EFb6