你知道前端水印功能是怎么实现的吗?(二)

简介: 你知道前端水印功能是怎么实现的吗?

使用 MutationObserver 优化

以上提到的两种前端实现方案,都存在一个问题很明显的问题,那就是用于只要用户通过 开发者调试工具 来稍微操作一,旧能够导致水印失效:

  • 删除对应 dom 节点
  • 设置对应 dom 节点的 css 样式

image.png

MutationObserver 接口提供对 DOM 树监听的能力,它能够监听 DOM 树属性、节点本身、子节点等的变化,于是优化的思路就是使用 MutationObserver 去监听外部对应 water-mark 节点的操作,只要监听到了就重新渲染水印效果即可。

效果和代码

注意】这里最容易踩坑的点就是 MutationObserver 中的条件写得不正确的话会导致死循环.

image.png

/********* src/directives/waterMark.ts  ***********/ 
// 全局保存 canvas 和 div ,避免重复创建(单例模式)
const globalCanvas = null;
const globalWaterMark = null;
// watermark 样式
let style = `
display: block;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-repeat: repeat;
pointer-events: none;`;
const getDataUrl = ({
  font = "16px normal",
  fillStyle = "rgba(180, 180, 180, 0.3)",
  textAlign,
  textBaseline,
  text = "请勿外传",
}) => {
  const rotate = -20;
  const canvas = globalCanvas || document.createElement("canvas");
  const ctx = canvas.getContext("2d"); // 获取画布上下文
  ctx.rotate((rotate * Math.PI) / 180);
  ctx.font = font;
  ctx.fillStyle = fillStyle;
  ctx.textAlign = textAlign || "left";
  ctx.textBaseline = textBaseline || "middle";
  ctx.fillText(text, canvas.width / 3, canvas.height / 2);
  return canvas.toDataURL("image/png");
};
const setWaterMark = (el: HTMLElement, binding: any = {}) => {
  const { parentElement } = el;
  // 获取对应的 canvas 画布相关的 base64 url
  const url = getDataUrl(binding);
  // 创建 waterMark 父元素
  const waterMark = globalWaterMark || document.createElement("div");
  waterMark.className = `water-mark`; // 方便自定义展示结果
  style = `${style}background-image: url(${url});`;
  waterMark.setAttribute("style", style);
  // 将对应图片的父容器作为定位元素
  parentElement.setAttribute("style", "position: relative;");
  // 将图片元素移动到 waterMark 中
  parentElement.appendChild(waterMark);
};
// 监听 DOM 变化
const createObserver = (el: HTMLElement, binding: any) => {
  const waterMarkEl = el.parentElement.querySelector(".water-mark");
  const observer = new MutationObserver((mutationsList) => {
    if (mutationsList.length) {
      const { removedNodes, type, target } = mutationsList[0];
      const currStyle = waterMarkEl.getAttribute("style");
      // 证明被删除了
      if (removedNodes[0] === waterMarkEl) {
        observer.disconnect();
        init(el, binding);
      } else if (
        type === "attributes" &&
        target === waterMarkEl &&
        currStyle !== style
      ) {
        waterMarkEl.setAttribute("style", style);
      }
    }
  });
  observer.observe(el.parentElement, {
    childList: true,
    attributes: true,
    subtree: true,
  });
};
// 初始化
const init = (el: HTMLElement, binding: any = {}) => {
  // 设置水印
  setWaterMark(el, binding.value);
  // 启动监控
  createObserver(el, binding.value);
};
// 定义指令配置项
const directives: any = {
  mounted(el: HTMLElement, binding: any) {
    el.onload = init.bind(null, el, binding);
  },
};
export default {
  name: "watermark",
  directives,
};
复制代码

最后

上述过程中我们做了那么多优化,最终的结果看起来比较还算是可以接受,但实际上前端的实现方案终归是不够完美的,比如有心人直接复制图片对应的地址怎么办?又或者是通过开发者调试工具选择禁用 JavaScript 又怎么办呢?

因此,总结下来前端的实现方案是属于:防君子不防小人 的方案,不过也不必过于纠结这一点,毕竟 语雀 这样的网站连 MutationObserver 都没加呢 ~~


目录
相关文章
|
3天前
|
JavaScript 前端开发 数据安全/隐私保护
【vue】 前端vue2 全局水印效果
【vue】 前端vue2 全局水印效果
6 0
|
2月前
|
前端开发 JavaScript 安全
【Web 前端】怎么实现Module模块化?
【5月更文挑战第1天】【Web 前端】怎么实现Module模块化?
|
2月前
|
前端开发 JavaScript
前端 JavaScript 与 HTML 怎么实现交互
前端 JavaScript 与 HTML 怎么实现交互
|
11月前
|
前端开发
【面试题】:前端怎么实现组件的封装和上传
前端如何实现组件的封装以及上传
164 0
|
2月前
|
前端开发 JavaScript 定位技术
Flutter vs 前端 杂谈:SliverAppBar、手动实现Appbar、前端Html+JS怎么实现滚动变化型Appbar - 比较
Flutter vs 前端 杂谈:SliverAppBar、手动实现Appbar、前端Html+JS怎么实现滚动变化型Appbar - 比较
47 0
|
9月前
|
缓存 前端开发 中间件
Go 框架 Gin 怎么实现允许前端跨域请求?
Go 框架 Gin 怎么实现允许前端跨域请求?
90 0
|
11月前
|
前端开发 JavaScript
前端 JavaScript 与 HTML 怎么实现交互
前端 JavaScript 与 HTML 怎么实现交互
127 0
|
前端开发 JavaScript API
不使用第三方库怎么实现【前端引导页】功能?
不使用第三方库怎么实现【前端引导页】功能?
179 0
|
JavaScript 前端开发 安全
你知道前端水印功能是怎么实现的吗?(一)
你知道前端水印功能是怎么实现的吗?
255 0
|
前端开发 数据库
eggjs 怎么实现返回 base64 图片的接口给前端回显头像?
eggjs 怎么实现返回 base64 图片的接口给前端回显头像?
248 0
eggjs 怎么实现返回 base64 图片的接口给前端回显头像?