无法删除的水印方案


实现代码

class WaterMark {
  constructor(text = "", options = {}) {
    // 默认样式
    const defaultStyles = {
      transform: 'rotate(-15deg)',
      position: 'absolute',
      overflow: 'hidden',
      zIndex: 9999999,
      opacity: 0.2,
      fontFamily: '微软雅黑',
      color: 'black',
      textAlign: 'center',
      padding: '10px 10px',
      whiteSpace: 'nowrap',
      display: 'block',
    };

    // 覆盖默认样式
    let { left = 220, top = 80, fontSize = 16, id = "", styles = {} } = options
    this.styles = { ...defaultStyles, ...styles };
    this.text = text;
    this.left = Number(left);
    this.top = Number(top);
    this.fontSize = Number(fontSize)
    this.id = id;

    this.createWatermark()
    window.addEventListener('resize', this.handleResize);
  }
  createMaskDiv(left, top) {
    const maskDiv = document.createElement('div');
    maskDiv.id = 'water_mask_id_0';
    let styles = { ...this.styles, left: `${left}px`, top: `${top}px`, fontSize: `${this.fontSize}px` }
    for (var key in styles) {
      maskDiv.style[key] = styles[key];
    }
    maskDiv.innerText = this.text;
    return maskDiv;
  }

  createWatermark() {
    let maskDiv = this.createMaskDiv(0, 0)
    document.body.appendChild(maskDiv);
    let width = maskDiv.offsetWidth
    let height = maskDiv.offsetHeight
    document.body.removeChild(maskDiv);

    const div = document.createElement('div');
    div.id = 'eb-watermark';
    div.style.cssText = `pointer-events: none !important; display: block !important; visibility: visible !important; position: fixed !important; top: 0 !important; left: 0 !important; opacity: 1 !important; z-index: 0 !important; transform: translateX(0) !important;`;
    const shadowRoot = div.attachShadow({ mode: 'open' });

    let left = width + this.left
    let top = height + this.top

    for (let i = 0; i < window.innerWidth; i += left) {
      for (let j = 0; j < window.innerHeight; j += top) {
        shadowRoot.appendChild(this.createMaskDiv(i, j));
      }
    }

    const observerConfig = { childList: true, subtree: true, attributes: true, attributeFilter: ['id', 'style'] };

    // 监听DOM改变
    const observer = new MutationObserver(this.handleMutation.bind(this));
    observer.observe(document.body, observerConfig);

    // 设置characterData监听元素的innerText改变
    const shadowRootObserver = new MutationObserver((mutations) => this.handleShadowRootMutation(mutations, shadowRoot).bind(this));
    shadowRootObserver.observe(shadowRoot, { ...observerConfig, characterData: true });

    document.body.appendChild(div);
  }

  handleMutation(mutations) {
    mutations.forEach((mutation) => {
      const watermark = document.getElementById('eb-watermark');
      if (mutation.removedNodes.length > 0 || mutation.target.id === 'eb-watermark') {
        if (!watermark) {
          this.createWatermark();
        }
      }
      if (mutation.type === 'attributes' && (mutation.target.id === 'eb-watermark' || mutation.attributeName === 'style')) {
        if (watermark) {
          watermark.remove();
        }
        this.createWatermark();
      }
    });

    const watermark = document.body.querySelector('#eb-watermark');
    if (!watermark || watermark.parentElement !== document.body || !watermark.shadowRoot || !watermark.shadowRoot.querySelector('#water_mask_id_0')) {
      if (watermark) {
        watermark.remove();
      }
      this.createWatermark();
    }
  }

  handleShadowRootMutation(mutations, shadowRoot) {
    mutations.forEach((mutation) => {
      if (mutation.removedNodes.length > 0 || mutation.type === 'attributes' || (mutation.type === 'characterData' && (mutation.target.parentNode === shadowRoot || mutation.target.parentNode.parentNode === shadowRoot))) {
        const watermark = document.getElementById('eb-watermark');
        if (watermark) {
          watermark.remove();
        }
        this.createWatermark();
      }
    });
  }

  handleResize() {
    const watermark = document.getElementById('eb-watermark');
    if (watermark) {
      watermark.remove();
    }
    this.createWatermark();
  }

}

export default WaterMark;

使用

import WaterMark from './watermark';

new WaterMark('我是水印啊');

待优化

  • 目前只支持插入到body的全屏水印,下一步优化支持指定id的div水印

  目录