前端水印生成方案

  原文出处:QQ音乐前端博客    伯乐在线


        网页水印生成解决方案

  通过canvas生成水印

  Canvas兼容性

前端水印生成方案_第1张图片

  这里我们用canvas来生成base64图片,通过CanIUse网站查询兼容性,如果在移动端以及一些管理系统使用,兼容性问题可以完全忽略。

  HTMLCanvasElement.toDataURL 方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi。

  如果画布的高度或宽度是0,那么会返回字符串“data:,”。

  如果传入的类型非“image/png”,但是返回的值以“data:image/png”开头,那么该传入的类型是不支持的。

  Chrome支持“image/webp”类型。具体参考HTMLCanvasElement.toDataURL

  具体代码实现如下:

 (function () {
      // canvas 实现 watermark
      function __canvasWM({
        // 使用 ES6 的函数默认值方式设置参数的默认取值
        // 具体参见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Default_parameters
        container = document.body,
        width = '200px',
        height = '150px',
        textAlign = 'center',
        textBaseline = 'middle',
        font = "20px microsoft yahei",
        fillStyle = 'rgba(184, 184, 184, 0.8)',
        content = '请勿外传',
        rotate = '30',
        zIndex = 1000
      } = {}) {
        var args = arguments[0];
        var 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();
        const watermarkDiv = document.createElement("div");
        watermarkDiv.setAttribute('style', `
          position:absolute;
          top:0;
          left:0;
          width:100%;
          height:100%;
          z-index:${zIndex};
          pointer-events:none;
          background-repeat:repeat;
          background-image:url('${base64Url}')`);
 
        container.style.position = 'relative';
        container.insertBefore(watermarkDiv, container.firstChild);
 
        
      });
 
      window.__canvasWM = __canvasWM;
    })();
 
    // 调用
    __canvasWM({
      content: 'QQMusicFE'
    })
前端水印生成方案_第2张图片Canvas实现网页水印效果标题

为了使这个方法更通用,兼容不同的引用方式,我们还可以加上这段代码:

    // 为了兼容不同的环境
      if (typeof module != 'undefined' && module.exports) {  //CMD
        module.exports = __canvasWM;
      } else if (typeof define == 'function' && define.amd) { // AMD
        define(function () {
          return __canvasWM;
        });
      } else {
        window.__canvasWM = __canvasWM;
      }

 

  这样似乎能满足我们的需求了,但是还有一个问题,稍微懂一点浏览器的使用或者网页知识的用户,可以用浏览器的开发者工具来动态更改DOM的属性或者结构就可以去掉了。这个时候有两个解决办法:

  1.监测水印div的变化,记录刚生成的div的innerHTML,每隔几秒就取一次新的值,一旦发生变化,则重新生成水印。但是这种方式可能影响性能;

  2.使用MutationObserver

  MutationObserver给开发者们提供了一种能在某个范围内的DOM树发生变化时作出适当反应的能力。

  MutationObserver兼容性

 

前端水印生成方案_第3张图片

 

  通过兼容性表可以看出高级浏览器以及移动浏览器支持非常不错。

  Mutation Observer API 用来监视 DOM 变动。DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。

  使用MutationObserver构造函数,新建一个观察器实例,实例的有一个回调函数,该回调函数接受两个参数,第一个是变动数组,第二个是观察器实例。MutationObserver 的实例的observe方法用来启动监听,它接受两个参数。

  第一个参数:所要观察的 DOM 节点,第二个参数:一个配置对象,指定所要观察的特定变动,有以下几种:

前端水印生成方案_第4张图片

        MutationObserver只能监测到诸如属性改变、增删子结点等,对于自己本身被删除,是没有办法的可以通过监测父结点来达到要求。因此最终改造之后代码为:

(function () {
      // canvas 实现 watermark
      function __canvasWM({
        // 使用 ES6 的函数默认值方式设置参数的默认取值
        // 具体参见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Default_parameters
        container = document.body,
        width = '300px',
        height = '200px',
        textAlign = 'center',
        textBaseline = 'middle',
        font = "20px Microsoft Yahei",
        fillStyle = 'rgba(184, 184, 184, 0.6)',
        content = '请勿外传',
        rotate = '30',
        zIndex = 1000
      } = {}) {
        const args = arguments[0];
        const canvas = document.createElement('canvas');
 
        canvas.setAttribute('width', width);
        canvas.setAttribute('height', height);
        const 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);
 
        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
          })
        }
 
      }
 
      if (typeof module != 'undefined' && module.exports) {  //CMD
        module.exports = __canvasWM;
      } else if (typeof define == 'function' && define.amd) { // AMD
        define(function () {
          return __canvasWM;
        });
      } else {
        window.__canvasWM = __canvasWM;
      }
    })();
 
    // 调用
    __canvasWM({
      content: 'QQMusicFE'
    });

  通过SVG生成水印

  SVG:可缩放矢量图形(英语:Scalable Vector Graphics,SVG)是一种基于可扩展标记语言(XML),用于描述二维矢量图形的图形格式。 SVG由W3C制定,是一个开放标准。 — 维基百科

  SVG浏览器兼容性

前端水印生成方案_第5张图片

        相比Canvas,SVG有更好的浏览器兼容性,使用SVG生成水印的方式与Canvas的方式类似,只是base64Url的生成方式换成了SVG。具体如下:

 

(function () {
      // svg 实现 watermark
      function __svgWM({
        container = document.body,
        content = '请勿外传',
        width = '300px',
        height = '200px',
        opacity = '0.2',
        fontSize = '20px',
        zIndex = 1000
      } = {}) {
        const args = arguments[0];
        const svgStr = `
  
    ${content}
  
`;
        const base64Url = `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgStr)))}`;
        const __wm = document.querySelector('.__wm');
 
        const watermarkDiv = __wm || document.createElement("div");
     // ...
     // 与 canvas 的一致
     // ...
    })();
 
    __svgWM({
      content: 'QQMusicFE'
    })

 

  通过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给图片加水印

  实现如下:

 

(function() {
      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);
        }
      }
 
        if (typeof module != 'undefined' && module.exports) {  //CMD
        module.exports = __picWM;
      } else if (typeof define == 'function' && define.amd) { // AMD
        define(function () {
          return __picWM;
        });
      } else {
        window.__picWM = __picWM;
      }
      
    })();
 
    // 调用
    __picWM({
        url: 'http://localhost:3000/imgs/google.png',
        content: 'QQMusicFE',
        cb: (base64Url) => {
          document.querySelector('img').src = base64Url
        },
      });

 效果如下:

前端水印生成方案_第6张图片Canvas给图片生成水印

 

 

  通过NodeJS批量为图片加水印

  我们同样可以通过gm这个库来给图片加上水印

 

function picWM(path, text) {
  imageMagick(path)
    .drawText(10, 50, text)
    .write(require('path').join(__dirname, `./${text}.png`), function (err) {
      if (err) {
        console.log(err);
      }
    });
}

 

  如果需要批处理图片,只需要遍历相关文件即可。

  如果只是简单的水印展示,建议在浏览器生成,性能更好

  拓展

  隐水印

  前段时间阿里凭截图查到了月饼事件的泄密者,其实就是用了隐水印。这其实很大程度不是前端的范畴了,但是我们也应该了解。AlloyTeam团队写过一篇 不能说的秘密——前端也能玩的图片隐写术 ,通过Canvas给图片加上了“隐水印”,针对用户保存的图片,是可以轻松还原里面隐含的内容,但是对于截图或者处理过的照片却无能为力,不过对于一些机密图片文件展示,是可以偷偷用上该技术的。

  使用加密后的水印内容

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

 

你可能感兴趣的:(前端,添加水印,前端,添加水印,canvas,NodeJS,SVG)