前端水印的实现

前端水印的实现

参考原文:https://juejin.cn/post/7302724955699822631

网页水印

实现思路

  1. 通过canvas生成一张水印图片
  2. 通过css将图片设置为目标节点的背景图
  3. 通过MutationObserver监听目标节点的类名变化,防止水印被删除

代码操作

  1. 通过canvas生成一张水印图片
function createImgBase(options) {
  const { content, width, height } = options;
  const canvasDom = document.createElement("canvas");
  let ctx = canvasDom.getContext("2d");
  canvasDom.width = width;
  canvasDom.height = height;
  if (ctx) {
    // 设置画笔的方向
    ctx.rotate((-14 * Math.PI) / 180);
    // 设置水印样式
    ctx.fillStyle = "rgba(100,100,100,0.4)";
    ctx.font = "italic 20px Arial";
    // 渲染水印
    content.forEach((text, index) => {
      ctx.fillText(text, 10, 30 * (index + 1)); // 纵向拉开30的间距
    });
  }
  // document.body.appendChild(canvasDom);
  // 将canvas转为图片
  return canvasDom.toDataURL("image/png");
}

// createImgBase({
//   content: ["介四嘛呀", "介四sui印", "内部机密材料", "严禁外泄!"],
//   width: 200,
//   height: 200,
// });
  1. 将水印设置为目标节点的背景图片
function getWaterMark({
  content,
  className,
  canvasHeight = 140,
  canvasWidth = 150,
}) {
  // 生成图片
  const data_url = createImgBase({
    content,
    width: canvasWidth,
    height: canvasHeight,
  });
  // 通过设置伪元素样式,添加水印图片为背景图
  const defaultStyle = `
  .${className} {
    position: relative;
  }
  .${className}::after {
    content: "";
    background-image: url(${data_url});
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    pointer-events: none;
  }`;

  const styleDom = document.createElement("style");
  styleDom.innerHTML = defaultStyle;
  document.head.appendChild(styleDom);
}
// getWaterMark({
//   content: ["介四嘛呀", "介四sui印", "内部机密材料", "严禁外泄!"],
//   className: "content",
// });
  1. 添加mutationObserver监听节点的变化
function listenerDOMChange(className) {
  // 获取要监听的节点
  const targetNode = document.querySelector(`.${className}`);
  // 创建监听器
  const observer = new MutationObserver(mutationList => {
    // 遍历变化记录
    for (let mutationRecord of mutationList) {
      // 如果目标节点的class属性发生变化,判断是不是类名被删了,是的话把类名加回去
      if (mutationRecord.attributeName === "class") {
        if(!Array.from(targetNode.classList).includes(className)) {
          targetNode.classList.add(className)
        }
      }
    }
  });
  // 启动监听
  observer.observe(targetNode, {
    attributes: true,
  });
}

function getWaterMark({
      content,
      className,
      canvasHeight = 140,
      canvasWidth = 150,
}) {
  // 监听
  listenerDOMChange(className);
  const data_url = createImgBase({
    content,
    width: canvasWidth,
    height: canvasHeight,
  });
  // ...
  const styleDom = document.createElement("style");
  styleDom.innerHTML = defaultStyle;
  document.head.appendChild(styleDom);
}

关于MutationObserver

MutationObserver 用来监听DOM的变化,DOM的增删、DOM属性的变化,子结点和文本内容的变化,都可以被监听。

MutationObserver 的监听和事件不同,事件是同步的,DOM的变化会立即触发对应的事件,而 MutationObserver 是异步的,会在下一个微任务执行时触发监听回调。

  1. 创建MutationObserver
const observer = new MutationObserver((mutationsList, observer) => {
    // mutationsList mutationRecord数组 记录了DOM的变化
    // observer MutationObserver的实例

    // 监听回调
    console.log(mutationsList, observer);
})
  1. 开启监听
// node 监听的节点
// config 监听配置(要监听哪些内容)
// observer.observe(node, config);
mutationObserver.observe(document.documentElement, {
  attributes: true,  // 属性变化
  attributeOldValue: true,  // 观察attributes变动时,是否需要记录变动前的属性值
  attributeFilter: [class,‘src’]  // 需要观察的特定属性

  characterData: true,  // 节点内容、文本的变化
  characterDataOldValue: true,  // 观察characterData变动时,是否需要记录变动前的属性值

  childList: true,  // 子结点变化
  subtree: true,    // 所有后代节点
});

// 停止监听
mutationObserver.disconnect()

// 清除变动记录
mutationObserver.takeRecords()

图片水印

实现思路

方案一:通过oss添加水印
方案二:通过canvas生成带有水印的图片

oss实现

oss方式不做过多描述了
简单来说就是通过在获取图片时,在图片链接上增加参数,让oss生成一张带水印的图片。
注意点:

  1. png图片的透明区域无法被添加水印。
    解决方式: 可通过添加参数的方式,让oss将图片转为jpg格式(jpg格式会对透明区域做颜色填充)。

  2. 字体大小写为定值,原图大小会影响到水印字体的显示大小。
    解决方式:通过创建img标签,onLoad获取图片后,根据图片宽高计算合适的字体大小,然后再一次获取带水印的图片。

  3. 用户通过删除参数的方式可以删除水印
    解决方式:设置oss的安全级别,不带水印不可访问。

canvas实现

  1. 将img转为canvas
async function imgToCanvas(cav, imgSrc) {
  const img = new Image();
  img.src = imgSrc;
  // 防止因跨域导致的图片加载失败(该方法有局限性)
  img.setAttribute("crossOrigin", "anonymous");
  // 等待图片加载
  await new Promise(resolve => (img.onload = resolve));
  cav.width = img.width;
  cav.height = img.height;
  const ctx = cav.getContext("2d");
  if (ctx) {
    ctx.drawImage(img, 0, 0);
  }
  return cav;
}
  1. 添加水印
function addWaterMask(cav, content) {
  const ctx = cav.getContext("2d");
  ctx.fillStyle = "rgba(100,100,100,0.2)";
  ctx.font = `24px serif`;
  ctx.translate(0, 0);
  ctx.rotate((5 * Math.PI) / 180);
  // 生成水印
  let x = 0, y = 0;
  while (x < cav.width) {
    y = 0;
    while (y < cav.height) {
      ctx.fillText(content, x, y);
      y += 100;
    }
    x += 150;
  }
}
  1. 使用
(async function () {
  const canvas = document.createElement("canvas");
  await imgToCanvas(
    canvas,
    "图片地址"
  );
  addWaterMask(canvas, "介四sui印");
  // document.body.appendChild(canvas);
  return canvas.toDataUrl("image/png")
})();
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值