美文网首页我爱编程
前端水印生成方案

前端水印生成方案

作者: wwmin_ | 来源:发表于2018-08-08 23:19 被阅读357次

    先看下效果:


    水印效果

    思路1:

    使用canvas进行生成图片,然后动态生成div填充整个背景,将生成的图片用与 background-image属性上,进行页面填充.
    问题:
    如果在开发者工具里取消了这个属性,则该水印效果就会消失,所以需要一种方法监视该div,不让其修改,或修改了之后又进行恢复,此处就用到了用来监视 DOM变动的Mutation Observer API .DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。参考MDN:MutationObserver
    MutationObserver只能监测到诸如属性改变、增删子结点等,对于自己本身被删除,是没有办法的,那么可以通过监测父结点来达到要求。

    代码:

    
    /**
     * 网页加水印
     *
     * @export
     * @param {*} [{
     *   container = document.body,
     *   width = '400px',
     *   height = '300px',
     *   textAlign = 'center',
     *   textBaseline = 'middle',
     *   font = "20px Microsoft Yahei",
     *   fillStyle = 'rgba(230, 230, 230, 0.8)',
     *   content = '保密水印',
     *   rotate = '10',
     *   zIndex = -1000
     * }={}]
     * @returns
     */
    export function __waterDocument({
      container = document.body,
      width = '200px',
      height = '150px',
      textAlign = 'center',
      textBaseline = 'middle',
      font = "20px Microsoft Yahei",
      fillStyle = 'rgba(230, 230, 230, 0.8)',
      content = '保密水印',
      rotate = '10',
      zIndex = -1000
    } = {}) {
      const args = arguments[0];
      const canvas = document.createElement('canvas');
      canvas.setAttribute('width', width);
      canvas.setAttribute('height', height);
      const ctx = canvas.getContext("2d");
      if (ctx === null) {
        console.error("this browser is not support canvas.");
        return;
      }
      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
        })
      }
    }
    

    思路2:

    如果浏览器对canvas不支持则可以使用svg,svg浏览器支持面比canvas要大一些
    可以使用https://caniuse.com/进行查看
    实现原理与canvas几乎一致,只是图片替换成了svg

    
    /**
     *网页加水印 svg 方式
     *
     * @export
     * @param {*} [{
     *   container = document.body,
     *   content = '请勿外传',
     *   width = '300px',
     *   height = '200px',
     *   opacity = '0.2',
     *   fontSize = '20px',
     *   zIndex = 1000
     * }={}]
     */
    export function __waterDocumentSvg({
      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");
      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;
            __waterDocumentSvg(JSON.parse(JSON.stringify(args)));
          }
        });
        mo.observe(container, {
          attributes: true,
          subtree: true,
          childList: true
        })
      }
    }
    

    通过NodeJS生成水印

    身为现代前端开发者,Node.JS也是需要掌握的。我们同样可以通过NodeJS来生成网页水印,前端发一个请求,参数带上水印内容,后台返回图片内容。
    具体实现(Koa2环境):

    1. 安装gm以及相关环境,详情看gm文档
    2. ctx.type = 'image/png';设置响应为图片类型
    3. 生成图片过程是异步的,所以需要包装一层Promise,这样才能为通过 async/await 方式为 ctx.body 赋值
    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`)))
              }
            });
        })
      })());
    });
    

    图片水印生成解决方案

    通过canvas给图片加水印
    思路:读取图片,在图片加载完之后用canvas填充图片,并填充加密文字,然后将图片转成base64url传值给回调函数

    /**
     *图片加水印
     *
     * @export
     * @param {*} [{
     *   url = '',
     *   textAlign = 'center',
     *   textBaseline = 'middle',
     *   font = "20px Microsoft Yahei",
     *   fillStyle = 'rgba(184, 184, 184, 0.8)',
     *   content = '请勿外传',
     *   cb = null,
     *   textX = 100,
     *   textY = 30
     * }={}]
     */
    export 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);
      }
    }
    

    调用:

    __picWM({
            url: 'https://www.baidu.com/img/bd_logo1.png?where=super',
            content: '加密图片',
            cb: (base64Url) => {
              document.getElementById('s_lg_img').src = base64Url
            },
          })
    

    效果:


    加密图片

    使用加密后的水印内容

    前端生成的水印也可以,别人也可以用同样的方式生成,可能会有“嫁祸于人”(可能这是多虑的),我们还是要有更安全的解决方法。水印内容可以包含多种编码后的信息,包括用户名、用户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));
    }
    

    参考:前端水印生成方案 --QQ音乐前端团队

    相关文章

      网友评论

        本文标题:前端水印生成方案

        本文链接:https://www.haomeiwen.com/subject/tbffbftx.html