前端水印使用方式

5,643 阅读3分钟

我们从实现方式上来考虑,既然要做水印,那肯定要是全屏幕的,我们会先想到几点

  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();
  });
})();