乐趣区

关于前端:前端水印如此简单

咱们从实现形式上来思考,既然要做水印,那必定要是全屏幕的,咱们会先想到几点

  1. 用一个 div 全屏 fixed。
  2. 水印要和登录信息绑定,那么咱们从 cookie 中获取一下账号信息。
  3. 屏幕上一个大的水印,成果没有稀稀拉拉的小水印成果好。
  4. 水印之前的间距要小一点,这样能力减少覆盖面积。
  5. 要有避免篡改的性能。

列出以上这几点,咱们就顺次实现就好了。

第一点

很好实现。咱们 create 一个 dom 元素,插入到 body 中就能够了。

 const divObj = document.createElement('div');
    const styleStr = `
                  position:fixed;
                  top:0;
                  left:0;
                  bottom:0;
                  right:0;
                  z-index:999999;
                  background-repeat:repeat;
                  `;
    divObj.setAttribute('style', styleStr);
    document.body.appendChild(divObj);

第二点

水印的内容咱们从 cookie 中获取一下。

 const user = /user_name=([^;]+)/.exec(document.cookie);
 const name = Array.isArray(user) && user.length === 2 && user[1] ? user[1] : '配置的水印';

这里的 name 就是咱们拿到的用户信息。

第三四点

这两个综合思考的话,将 name 作为一个背景图 而后 repeat 就能够了。咱们要将文字转为图片,那么 canvas 是一个不错的抉择。同时咱们要思考到这个图片不易过大(为了合乎第三点),所以咱们就依照 200*100 的尺寸吧。

    const canvasObj = document.createElement('canvas');
    const canvas2d = canvasObj.getContext('2d');
    canvasObj.width = 200;
    canvasObj.height = 100;
    canvas2d.font = fontSize + 'px Arial';
    canvas2d.fillStyle = 'rgba(128,128,128,.6)'; // 这里文字的色彩淡一点,不要影响整体的好看
    canvas2d.translate(canvasObj.width / 4, canvasObj.height / 2);
    canvas2d.rotate((-30 / 180) * Math.PI);
    canvas2d.fillText(name, 0, canvasObj.height / 2);
    // 将 canvas 转为 dataURL
    const base64Url = canvasObj.toDataURL('image/png');

当初咱们就拿到了一个 base64 的地址,能够用来作为咱们的图片。

第五点

要有防篡改的性能,具体要体现在咱们创立的水印不能轻易的让他人给删了,其次,水印内容也不能轻易的被改了,无论是水印内容或者是水印的色彩款式等。

水印内容这一块因为咱们应用的是 cookie 中的登录信息,如果有人更改了 cookie 中的值,会导致登录信息生效,在 sso 这一侧就会被强制跳转到登录页面,所以这个能够交由登录零碎来做。

咱们的 dom 不能轻易的被删除和更改款式,那么咱们就须要用到 MutationObserver 这个 api 了,他的作用就是监听 DOM 的变动,并触发一个回调,咱们只需在回调中从新执行水印的办法就能够防止这个问题。

   if (MutationObserver) {let waterMarkOb = new MutationObserver(function () {const _globalWatermark = document.querySelector(`domId`);
        // 当款式或者水印元素 dom 节点有改变时会从新绘制
        if ((_globalWatermark && _globalWatermark.getAttribute('style') !== styleStr) ||
          !_globalWatermark
        ) {waterMarkOb.disconnect();
          waterMarkOb = null;
          setWaterMark();}
      });
      // 指定察看对象
      waterMarkOb.observe(document.body, {
        attributes: true,
        subtree: true,
        childList: true,
      });
    }

最初一点

注意事项中有一点,就是咱们的水印性能尽可能的不要影响到业务的应用。
所以这里咱们要思考两点:

  1. 咱们的水印 dom 在全屏幕的最上层,尽管层级在 99999,然而也不要要影响到上面的元素操作。所以咱们须要减少 pointer-events:none 的属性,不影响鼠标的操作。
  2. 咱们要在前端代码不变动的状况下上线水印的性能,那咱们就要从服务器上动手了,比方在 nginx 中,咱们能够应用 sub_filter 模块来替换返回的文本。例如

    subs_filter "(<\/body>)" "$1<script src=\"https://cdn.xxx.com/watermark.js\"></script>" irg;

    到此咱们的水印性能就算实现了。
    一贯的准则,「BB is nothing,show me the code」。
    整体的代码如下:

!(function () {
  // 一个配置
  const options = {
    id: 'globalWaterMark',
    fontSize: 10,
    color: 'rgba(128,128,128,.6)',
    rotate: '-30',
    userName: "其余的身份"
  };

  /**
   * 创立水印图片 url
   */
  function createWaterMark() {const { fontSize, color, id} = options;
    const user = /user_name=([^;]+)/.exec(document.cookie);
    const name = Array.isArray(user) && user.length === 2 && user[1] ? user[1] : options.userName;
    const canvasObj = document.createElement('canvas');
    const canvas2d = canvasObj.getContext('2d');
    canvasObj.width = 200;
    canvasObj.height = 100;
    canvas2d.font = fontSize + 'px Arial';
    canvas2d.fillStyle = color;
    canvas2d.translate(canvasObj.width / 4, canvasObj.height / 2);
    canvas2d.rotate((-30 / 180) * Math.PI);
    canvas2d.fillText(name, 0, canvasObj.height / 2);
    // 将 canvas 转为 dataURL
    const base64Url = canvasObj.toDataURL('image/png');
    return base64Url;
  }

  function setWaterMark() {const { fontSize, color, id} = options;
    const url = createWaterMark();
    const target = document.getElementById(id);
    if(target){document.body.removeChild(target)
    }
    const divObj = document.createElement('div');
    divObj.id = options.id;
    const styleStr = `
                  position:fixed;
                  top:0;
                  left:0;
                  bottom:0;
                  right:0;
                  z-index:999999;
                  background-repeat:repeat;
                  pointer-events:none;
                  background-image:url('${url}')`;
    divObj.setAttribute('style', styleStr);
    document.body.appendChild(divObj);
    // 监听 DOM 变动
    const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
    if (MutationObserver) {let waterMarkOb = new MutationObserver(function () {const _globalWatermark = document.querySelector(`#${id}`);
        // 当款式或者水印元素 dom 节点有改变时会从新绘制
        if ((_globalWatermark && _globalWatermark.getAttribute('style') !== styleStr) ||
          !_globalWatermark
        ) {waterMarkOb.disconnect();
          waterMarkOb = null;
          setWaterMark();}
      });
      // 指定察看对象
      waterMarkOb.observe(document.body, {
        attributes: true,
        subtree: true,
        childList: true,
      });
    }
  }

  document.addEventListener('DOMContentLoaded', function () {setWaterMark();
  });
})();

退出移动版