Skip to content

学习antd、百度 wartermark 实现

Posted on:April 25, 2023 at 11:03 AM

最近要实现一个页面内的水印效果,想了一下主要问题在以下几个方面:

如何实现文字水印效果?

1. 通过坐标计算 遍历生成文字水印元素

    // 百度 文心一言页面水印生成部分代码
    function a(e, t) {
        var n, a, i;
        function o(e) {
            return o = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(e) {
                return typeof e
            }
            : function(e) {
                return e && "function" == typeof Symbol && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e
            }
            ,
            o(e)
        }
        a = [],
        n = function() {
            var e, t = {}, n = !1, a = {
                watermark_id: "wm_div_id",
                watermark_prefix: "mask_div_id",
                watermark_txt: "测试水印",
                watermark_x: 20,
                watermark_y: 20,
                watermark_rows: 0,
                watermark_cols: 0,
                watermark_x_space: 50,
                watermark_y_space: 50,
                watermark_font: "微软雅黑",
                watermark_color: "black",
                watermark_fontsize: "18px",
                watermark_alpha: .15,
                watermark_width: 100,
                watermark_height: 100,
                watermark_angle: 15,
                watermark_parent_width: 0,
                watermark_parent_height: 0,
                watermark_parent_node: null,
                watermark_nowrap: !1,
                monitor: !0
            }, i = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver, r = function(t) {
                n ? n = !1 : (e && 1 === t.length || 1 === t.length && t[0].removedNodes.length >= 1) && u(e)
            }, l = void 0 !== i, s = l ? new i(r) : null, c = {
                childList: !0,
                attributes: !0,
                subtree: !0
            }, u = function() {
                for (var e = arguments.length, t = new Array(e), n = 0; n < e; n++)
                    t[n] = arguments[n];
                if (1 === t.length && "object" === o(t[0])) {
                    var i = t[0] || {};
                    for (key in i)
                        i[key] && a[key] && i[key] === a[key] || (i[key] || 0 === i[key]) && (a[key] = i[key])
                }
                var r = document.getElementById(a.watermark_id);
                r && r.parentNode && r.parentNode.removeChild(r);
                var u = document.getElementById(a.watermark_parent_node)
                  , d = u || document.body
                  , m = Math.max(d.scrollWidth, d.clientWidth)
                  , p = Math.max(d.scrollHeight, d.clientHeight)
                  , v = t[0] || {}
                  , h = d
                  , f = 0
                  , y = 0;
                v.watermark_parent_width || v.watermark_parent_height ? h && (f = h.offsetTop || 0,
                y = h.offsetLeft || 0,
                a.watermark_x = a.watermark_x + y,
                a.watermark_y = a.watermark_y + f) : h && (f = h.offsetTop || 0,
                y = h.offsetLeft || 0);
                var g = document.getElementById(a.watermark_id)
                  , E = null;
                if (g)
                    g.shadowRoot && (E = g.shadowRoot);
                else {
                    (g = document.createElement("div")).id = a.watermark_id,
                    g.setAttribute("style", "pointer-events: none !important; display: block !important; visibility: visible !important; opacity: 1 !important; position: static !important"),
                    E = "function" == typeof g.attachShadow ? g.attachShadow({
                        mode: "closed"
                    }) : g;
                    var x = d.children
                      , w = Math.floor(Math.random() * (x.length - 1));
                    x[w] ? d.insertBefore(g, x[w]) : d.appendChild(g)
                }
                a.watermark_cols = parseInt((m - a.watermark_x) / (a.watermark_width + a.watermark_x_space), 10);
                var k, b = parseInt((m - a.watermark_x - a.watermark_width * a.watermark_cols) / a.watermark_cols, 10);
                a.watermark_x_space = b ? a.watermark_x_space : b,
                a.watermark_rows = parseInt((p - a.watermark_y) / (a.watermark_height + a.watermark_y_space), 10);
                var S, I, C, N = parseInt((p - a.watermark_y - a.watermark_height * a.watermark_rows) / a.watermark_rows, 10);
                a.watermark_y_space = N ? a.watermark_y_space : N,
                u ? (k = a.watermark_x + a.watermark_width * a.watermark_cols + a.watermark_x_space * (a.watermark_cols - 1),
                S = a.watermark_y + a.watermark_height * a.watermark_rows + a.watermark_y_space * (a.watermark_rows - 1)) : (k = y + a.watermark_x + a.watermark_width * a.watermark_cols + a.watermark_x_space * (a.watermark_cols - 1),
                S = f + a.watermark_y + a.watermark_height * a.watermark_rows + a.watermark_y_space * (a.watermark_rows - 1));
                for (var T = 0; T < a.watermark_rows; T++) {
                    C = u ? f + a.watermark_y + (p - S) / 2 + (a.watermark_y_space + a.watermark_height) * T : a.watermark_y + (p - S) / 2 + (a.watermark_y_space + a.watermark_height) * T;
                    for (var _ = 0; _ < a.watermark_cols; _++) {
                        I = u ? y + a.watermark_x + (m - k) / 2 + (a.watermark_width + a.watermark_x_space) * _ : a.watermark_x + (m - k) / 2 + (a.watermark_width + a.watermark_x_space) * _;
                        var B = document.createElement("div")
                          , D = document.createTextNode(a.watermark_txt);
                        B.appendChild(D),
                        B.id = a.watermark_prefix + T + _,
                        B.style.webkitTransform = "rotate(-" + a.watermark_angle + "deg)",
                        B.style.MozTransform = "rotate(-" + a.watermark_angle + "deg)",
                        B.style.msTransform = "rotate(-" + a.watermark_angle + "deg)",
                        B.style.OTransform = "rotate(-" + a.watermark_angle + "deg)",
                        B.style.transform = "rotate(-" + a.watermark_angle + "deg)",
                        B.style.visibility = "",
                        B.style.position = "absolute",
                        B.style.left = I + "px",
                        B.style.top = C + "px",
                        B.style.overflow = "hidden",
                        B.style.zIndex = "9999999",
                        B.style.opacity = a.watermark_alpha,
                        B.style.fontSize = a.watermark_fontsize,
                        B.style.fontFamily = a.watermark_font,
                        B.style.color = a.watermark_color,
                        B.style.textAlign = "center",
                        B.style.width = a.watermark_width + "px",
                        B.style.height = a.watermark_height + "px",
                        B.style.display = "block",
                        B.style["-ms-user-select"] = "none",
                        a.watermark_nowrap && (B.style.overflow = "initial",
                        B.style["white-space"] = "nowrap"),
                        E.appendChild(B)
                    }
                }
                (void 0 === t[0].monitor ? a.monitor : t[0].monitor) && l && s.observe(d, c)
            }, d = function() {
                if (1 === arguments.length && "object" === o(arguments.length <= 0 ? void 0 : arguments[0])) {
                    var e = (arguments.length <= 0 ? void 0 : arguments[0]) || {};
                    for (key in e)
                        e[key] && a[key] && e[key] === a[key] || (e[key] || 0 === e[key]) && (a[key] = e[key])
                }
                var t = document.getElementById(a.watermark_id);
                t.parentNode.removeChild(t),
                s.disconnect()
            };
            t.init = function(t) {
                e = t,
                u(t),
                window.addEventListener("onload", (function() {
                    u(t)
                }
                )),
                window.addEventListener("resize", (function() {
                    u(t)
                }
                ))
            }
            ,
            t.load = function(t) {
                e = t,
                u(t)
            }
            ,
            t.remove = function() {
                n = !0,
                d()
            }
            ,
            s = new i((function(t) {
                if (e && 1 === t.length || 1 === t.length && t[0].removedNodes.length >= 1)
                    u(e);
                else {
                    var n = document.getElementById(a.watermark_parent_node);
                    if (n) {
                        var i = getComputedStyle(n).getPropertyValue("width")
                          , o = getComputedStyle(n).getPropertyValue("height");
                        i === m.width && o === m.height || (m.width = i,
                        m.height = o,
                        u(e))
                    }
                }
            }
            )),
            c = {
                childList: !0,
                attributes: !0,
                subtree: !0,
                attributeFilter: ["style", "class"],
                attributeOldValue: !0
            };
            var m = {
                width: 0,
                height: 0
            };
            return t
        }(),
        void 0 === (i = "function" == typeof n ? n.apply(t, a) : n) || (e.exports = i)
    }

2. 使用canvas画出水印图片

interface WatermarkOptions {
  text: string;
  fontSize?: string;
  color?: string;
  angle?: number;
  spacing?: number;
}

class Watermark {
  private readonly options: WatermarkOptions;
  private readonly container: HTMLElement;

  constructor(container: HTMLElement, options: WatermarkOptions) {
    this.container = container;
    this.options = {
      fontSize: '24px',
      color: 'rgba(0, 0, 0, 0.1)',
      angle: -30,
      spacing: 200,
      ...options,
    };
    this.init();
  }

  private init() {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    if (ctx) {
      const { text, fontSize, color, angle, spacing } = this.options;
      const canvasWidth = this.container.offsetWidth + spacing;
      const canvasHeight = this.container.offsetHeight + spacing;
      canvas.width = canvasWidth;
      canvas.height = canvasHeight;

      ctx.font = fontSize + ' Arial';
      ctx.fillStyle = color;
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';

      const textWidth = ctx.measureText(text).width;
      const textHeight = parseInt(fontSize) + 10;
      const diagonalLength = Math.sqrt(canvasWidth * canvasWidth + canvasHeight * canvasHeight);

      ctx.translate(canvasWidth / 2, canvasHeight / 2);
      ctx.rotate((angle * Math.PI) / 180);
      for (let x = -diagonalLength; x < diagonalLength; x += textWidth + spacing) {
        for (let y = -diagonalLength; y < diagonalLength; y += textHeight + spacing) {
          ctx.fillText(text, x, y);
        }
      }

      const dataUrl = canvas.toDataURL();
      this.container.setAttribute("style", "pointer-events: none !important; display: block !important; visibility: visible !important; opacity: 1 !important; position: fixed !important")
      this.container.style.backgroundImage = `url(${dataUrl})`;
    }
  }
}

export default Watermark;

如何将文字水印图片设置为背景,且不影响界面正常交互?

css 属性 pointer-events: none

pointer-events 属性用于设置元素是否对鼠标事件做出反应。

默认值:	none
默认值:	auto
继承:	无
动画:	no。
版本:	CSS3
JavaScript 语法:	object.style.all="initial" 
CSS 语法
pointer-events: auto|none;
auto	默认值,设置该属性链接可以正常点击访问。
none	元素不能对鼠标事件做出反应
initial	设置该属性为它的默认值,查看更多initial
inherit	从父元素继承该属性, 查看更多 inherit

怎么尽可能的防止去除水印。

这个问题之前一直思考过,难点就是如何监听开发者工具是否被打开? 网上搜了发现相关文章比较少,但还是找到一篇比较靠谱的方案:

可以查阅 前端 Chrome 反调试方法 最终实现代码:

const consoleOpenCallback = () => {
  window.location = 'about:blakn';
}
setInterval(() => {
  const before = new Date();
  (function(){}).constructor('debugger')();
  // debugger;
  const after = new Date();
  const cost = after.getTime() - before.getTime();
  if (cost > 100) {
    consoleOpenCallback();
  }
}, 1000)

如何防止别人反反调试 绕过上面的检测去删除水印元素?

通过 MutationObserver API 来监听 DOM 节点的删除。MutationObserver API 提供了一种异步机制,可以观察 DOM 树的变化,并在节点被删除时触发回调函数。

以下是一个示例代码,演示如何使用 MutationObserver 监听 DOM 节点的删除:

// 选择要观察变化的目标节点
const targetNode = document.getElementById('target');

// 创建一个 MutationObserver 实例
const observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
      console.log('节点被删除了:', mutation.removedNodes[0]);
      // 在这里将删除的元素再次append到目标节点父元素即可
    }
  });
});

// 配置观察选项
const config = { childList: true };

// 开始观察目标节点
observer.observe(targetNode, config);