我爱编程

前端水印生成方案

2018-08-08  本文已影响357人  wwmin_

先看下效果:


水印效果

思路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音乐前端团队

上一篇下一篇

猜你喜欢

热点阅读