制作水印

一、生成水印的具体方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    function toDataURL(text) {
        let canvas = document.createElement('canvas');
        document.body.appendChild(canvas);
        canvas.width = 150;
        canvas.height = 60;
        let ctx = canvas.getContext('2d');
        if (!ctx)return;
        canvas.style.display = 'none';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.font = '12px sans-serif';
        ctx.fillStyle = '#1B1C33';
        ctx.globalAlpha = 0.1;
        ctx.translate(80, 40);
        ctx.rotate(-15 * (Math.PI / 180));
        ctx.fillText(text, 0, 0);
        let dataURL = canvas.toDataURL();
        canvas.remove();
        return dataURL;
    }
    function markElement(element, text) {
        let imageURI = toDataURL(text);
        element.style.cssText = 'background-image: url('+imageURI+');background-repeat:space repeat';
    }
    function waterMark(element, text) {
        element = element || document.body;
        markElement(element, text || '水印');
    }

调用:

1
    waterMark(document.body, '臭猪皮!!');

执行waterMark方法后,可以看到页面的效果:

图片

这样就实现了一个简单的创建水印的逻辑,但是在日常项目应用中,会存在几个问题:

  • 如果此时dom没有加载完毕,拿不到某个想要设置水印的元素怎么办?

  • 如果需要的水印文案需要异步获取该如何解决?

  • 如何应对一些高级用户修改设置水印的dom元素情况?

二、问题解决

1、dom是否加载完毕

我们都知道,document有一个readyState属性,可以用来判断dom是否加载完毕,readyState有三个值:

1)loading:正在加载,此状态下的document 仍在加载。

2)interactive:可交互,此状态下的文档已被解析,”正在加载”状态结束,但是诸如图像,样式表和框架之类的子资源仍在加载。

3)complete:已完成,此状态下的文档和所有子资源已完成加载。表示 load 状态的事件即将被触发。

当document.readyState发生变化时,将会触发document对象上的readystatechange事件。

因此可以通过在readystatechange事件中判断readyState的值来明确dom是否加载完毕。

1
2
3
4
5
6
7
8
9
10
11
    function domReady(cb) {
        if (document.readyState === 'loading') {//当还在加载时,给document绑定一个DOMContentLoaded事件
            document.addEventListener('DOMContentLoaded', function fn() {
                document.removeEventListener('DOMContentLoaded', fn);
                // todo sth
            });
        }
        else {//否则执行回调
            // todo sth
        }
    }

DOMContentLoaded事件在纯HTML被完全加载以及解析时触发,不必等样式表、图片或自框架完成加载。

因此这里当页面初始化,readyState为loading时监听DOMContentLoaded,该事件被触发后执行回调,或者当readyState不为loading时直接执行回调。

onreadystatechange的其他应用:

1
2
3
4
5
6
    // 模拟 DOMContentLoaded 和 jquery ready
    document.onreadystatechange = function () {
        if (document.readyState === "interactive") {
            //todo sth
        }
    }
1
2
3
4
5
6
// 模拟 load 事件
    document.onreadystatechange = function () {
        if (document.readyState === "complete") {
            //todo sth
        }
    }

2、异步获取文案

一般水印的文案都是用户名加手机号或者其他编号拼接成,因此需要从服务端获取,但由于异步获取数据会存在一个大问题,比如需要跨域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    jsonp(XXX_API, {}, (err, res) => {
        if (err) {
            console.error(err);
            return;
        }
        let data = res.data;
        let displayName = data.displayName, userPhone = data.userPhone, jobnumber = data.jobnumber;
        let userInfo = displayName + " " + (jobnumber || userPhone);
        domReady(() => {
            appendStyle(userInfo);
            waterMark(document.body, userInfo);
        });
    });
    
    function appendStyle(text) {
        var style = document.createElement('style');
        var imageURI = toDataURL(text);
        style.innerHTML = "\n  .water-mark {\n    background-image: url(" + imageURI + ");\n    background-repeat: space repeat;\n  }\n  ";
        document.head.appendChild(style);
    }

mutationObserver具体方法:

MutationObserver接口提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。

它的特点是:异步触发,DOM发生变动以后,并不会马上触发,而是要等到当前所有DOM操作都结束后才触发。

它包含三个方法:

1)disconnect: 阻止 MutationObserver 实例继续接收的通知,直到再次调用其observe()方法,该观察者对象包含的回调函数都不会再被调用。

2)observe: 配置MutationObserver在DOM更改匹配给定选项时,通过其回调函数开始接收通知。

3)takeRecords: 从MutationObserver的通知队列中删除所有待处理的通知,并将它们返回到MutationRecord对象的新Array中。

Safari 6.0和Chrome 18-25使用这个API的时候,需要加上WebKit前缀(WebKitMutationObserver)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
const MutationObserver =
    window.MutationObserver ||
    window.WebKitMutationObserver ||
    window.MozMutationObserver;

const supportMutationObserver = !!MutationObserver;//检测是否支持MutationObserver

const watermarkDefaultConfig = {
    attributes: true,
    attributeOldValue: true,
    childList: true,
};

function watermarkCallback(mutationList, observer) {
    mutationList.forEach(mutationRecord => {//回调接收records数组作为参数
        const {
            type,
            attributeName,
            target,
            oldValue,
            addedNodes,
            removedNodes,
        } = mutationRecord;
        if (type === 'attributes') {//如果是监听的属性
            observer.disconnect();//为什么要disconnect? 应该是要做一个停止操作,否则循环监听 会陷入死循环
            if (attributeName === 'style') {//设置回原来的style
                target.style = oldValue;
            } else if (attributeName === 'id') {//设置回原来的id
                target.id = oldValue;
            }
            observer.observe(target, watermarkDefaultConfig);
        }
        //如果包含子节点 进行递归
        if (type === 'childList' && !addedNodes[0] && removedNodes[0]) {
            //wm_是特殊的插入水印的dom标记 判断删除的节点s中是否有水印的dom,有的话重新插入
            Array.prototype.forEach.call(removedNodes, item => {
                if (item.id.match(/^wm_/)) {
                    observer.disconnect();
                    document.body.append(item);
                    observer.observe(target, watermarkDefaultConfig);
                }
            });
        }
    });
}

function initObserver($el, config) {
    if (!$el) return;
    if (supportMutationObserver) {
        const om = new MutationObserver(watermarkCallback);
        om.observe($el, watermarkDefaultConfig);
    }
}

执行initObserver,传入要监听的dom对象即可。

参考文档: HTML5新特性之Mutation Observer