HTML 项目页面添加水印

597 阅读4分钟

项目背景:react + antd 框架下,开发的,项目已经基于雏形。

项目要求:项目产品经理要求在已有的项目页面上添加水印,防止用户截图可以追踪起源,所以就打算添加水印的需求。

刚接到这个需求时,在网上找到了一些水印的生成,再结合当前项目进行调整,想着,在页面上层添加一个浮层,全部由水印打的,然后在通过pointerEvents = 'none'; 让水印不遮挡页面的点击事件。

水印的生成代码如下:

// 水印
// eslint-disable-next-line no-shadow-restricted-names
export const watermark = () => {
  const currentElement = document.getElementById('body');
  // 默认设置
  const defaultSettings = {
    watermark_txt: 'text', // 填充的内容
    watermark_x: 100, // 水印起始位置x轴坐标
    watermark_y: 20, // 水印起始位置Y轴坐标
    watermark_rows: 20, // 水印行数
    watermark_cols: 20, // 水印列数
    watermark_x_space: 100, // 水印x轴间隔
    watermark_y_space: 50, // 水印y轴间隔
    watermark_color: '#aaa', // 水印字体颜色
    watermark_alpha: 0.4, // 水印透明度
    watermark_fontsize: '15px', // 水印字体大小
    watermark_font: '微软雅黑', // 水印字体
    watermark_width: 210, // 水印宽度
    watermark_height: 80, // 水印长度
    watermark_angle: 20, // 水印倾斜度数
  };

  if (document.getElementById('otemp_water')) {
    document.getElementById('otemp_water').remove();
  }
  const oTemp = document.createElement('div');
  oTemp.id = 'otemp_water';
  // todo
  oTemp.style.pointerEvents = 'none';
  oTemp.style.position = 'absolute';
  oTemp.style.top = '0';
  oTemp.style.zIndex = '999';
  oTemp.style.zIndex = '999';
  oTemp.style.flex = 'auto';
  oTemp.style.width = 'calc(100% - 210px)';
  oTemp.style.overflow = 'hidden';
  oTemp.style.height = '1000%';

  // 获取页面最大宽度
  let page_width = Math.max(document.body.scrollWidth, document.body.clientWidth);
  const cutWidth = page_width * 0.015;
  page_width -= cutWidth;
  // 获取页面最大高度
  let page_height = Math.max(document.body.scrollHeight, document.body.clientHeight);
  page_height = Math.max(page_height, window.innerHeight - 30);

  oTemp.style.height = `${page_height}px`;
  // 如果将水印列数设置为0,或水印列数设置过大,超过页面最大宽度,则重新计算水印列数和水印x轴间隔
  if (
    defaultSettings.watermark_cols === 0 ||
    parseInt(
      defaultSettings.watermark_x +
        defaultSettings.watermark_width * defaultSettings.watermark_cols +
        defaultSettings.watermark_x_space * (defaultSettings.watermark_cols - 1),
      10
    ) > page_width
  ) {
    defaultSettings.watermark_cols = parseInt(
      (page_width - defaultSettings.watermark_x + defaultSettings.watermark_x_space) /
        (defaultSettings.watermark_width + defaultSettings.watermark_x_space),
      10
    );
    defaultSettings.watermark_x_space = parseInt(
      (page_width -
        defaultSettings.watermark_x -
        defaultSettings.watermark_width * defaultSettings.watermark_cols) /
        (defaultSettings.watermark_cols - 1),
      10
    );
  }
  // 如果将水印行数设置为0,或水印行数设置过大,超过页面最大长度,则重新计算水印行数和水印y轴间隔
  if (
    defaultSettings.watermark_rows === 0 ||
    parseInt(
      defaultSettings.watermark_y +
        defaultSettings.watermark_height * defaultSettings.watermark_rows +
        defaultSettings.watermark_y_space * (defaultSettings.watermark_rows - 1),
      10
    ) > page_height
  ) {
    defaultSettings.watermark_rows = parseInt(
      (defaultSettings.watermark_y_space + page_height - defaultSettings.watermark_y) /
        (defaultSettings.watermark_height + defaultSettings.watermark_y_space),
      10
    );
    defaultSettings.watermark_y_space = parseInt(
      (page_height -
        defaultSettings.watermark_y -
        defaultSettings.watermark_height * defaultSettings.watermark_rows) /
        (defaultSettings.watermark_rows - 1),
      10
    );
  }
  let x = 0;
  let y = 0;

  for (let i = 0; i < defaultSettings.watermark_rows; i += 1) {
    y =
      defaultSettings.watermark_y +
      (defaultSettings.watermark_y_space + defaultSettings.watermark_height) * i;
    for (let j = 0; j < defaultSettings.watermark_cols; j += 1) {
      x =
        defaultSettings.watermark_x +
        (defaultSettings.watermark_width + defaultSettings.watermark_x_space) * j;
      const mask_div = document.createElement('div');
      mask_div.id = `mask_div${i}${j}`;
      mask_div.className = 'mask_div';
      mask_div.appendChild(document.createTextNode(defaultSettings.watermark_txt));
      // 设置水印div倾斜显示
      mask_div.style.webkitTransform = `rotate(-${defaultSettings.watermark_angle}deg)`;
      mask_div.style.MozTransform = `rotate(-${defaultSettings.watermark_angle}deg)`;
      mask_div.style.msTransform = `rotate(-${defaultSettings.watermark_angle}deg)`;
      mask_div.style.OTransform = `rotate(-${defaultSettings.watermark_angle}deg)`;
      mask_div.style.transform = `rotate(-${defaultSettings.watermark_angle}deg)`;
      mask_div.style.visibility = '';
      mask_div.style.position = 'absolute';
      mask_div.style.left = `${x}px`;
      mask_div.style.top = `${y}px`;
      mask_div.style.overflow = 'hidden';
      mask_div.style.zIndex = '9999';
      // 让水印不遮挡页面的点击事件
      mask_div.style.pointerEvents = 'none';
      mask_div.style.opacity = defaultSettings.watermark_alpha;
      mask_div.style.fontSize = defaultSettings.watermark_fontsize;
      mask_div.style.fontFamily = defaultSettings.watermark_font;
      mask_div.style.color = defaultSettings.watermark_color;
      mask_div.style.textAlign = 'center';
      mask_div.style.width = `${defaultSettings.watermark_width}px`;
      mask_div.style.height = `${defaultSettings.watermark_height}px`;
      mask_div.style.display = 'block';
      oTemp.appendChild(mask_div);
    }
  }
  currentElement.appendChild(oTemp);
};

水印生成好之后,就是讲水印放在对应的页面上,通过const currentElement = document.getElementById('body');这个currentElement 就是我们需要把 对应的水印放在哪一个DOM树下。

弯道一 使用 componentWillReceiveProps 生命周期,通过路由的变化进行页面水印填充

刚开始是通过在路由填充页面,通过componentWillReceiveProps 生命周期,在路由发生变化时,再将水印重新填充和刷新。

import { watermark } from '@/utils/utils';

...

componentWillReceiveProps() {
    watermark();
  }

但是发现一个问题,因为componentWillReceiveProps 是在路由发生变化时,会进行调用,但是页面填充 render 时,页面会进行数据的刷新,导致页面高度发生变化,这样就会导致,在调用watermark() 函数时,不能正确获取页面的可视高度,导致水印填充不完美,要么超过页面高度,要么获取的高度太低,填充不满。

弯道二 通过监听页面宽高的变化,进行填充水印

import { watermark } from '@/utils/utils';

...

componentDidMount() {
    if (window.attachEvent) {
      window.attachEvent('onresize', () => watermark());
    } else if (window.addEventListener) {
      window.addEventListener('resize', () => watermark(), false);
    }
  }
  
  componentWillUnmount() {
    if (window.detachEvent) {
      window.detachEvent('onresize', () => watermark());
    } else if (window.removeEventListener) {
      window.removeEventListener('resize', () => watermark(), false);
    }
  }

onresize 事件会在窗口或者框架被调整大小时发生变化。 虽然说框架大小也会发生变化,但是实际发现只有在窗口大小发生变化时,才会执行这个方法,并且页面刷新的时候是不会执行当前方法的,所以这个方法又不行!!!

正道

参考:zhuanlan.zhihu.com/p/41418813

import ResizeObserver from 'resize-observer-polyfill';
import { watermark } from '@/utils/utils';
...
constructor(props) {
    super(props);
    this.resizeObserver = undefined;
  }
componentDidMount() {
    this.resizeObserver = new ResizeObserver(() => {
      watermark();
    });
    // 当document.getElementById('body') DOM 宽高发生变化时,
    this.resizeObserver.observe(document.getElementById('body'));
  }
  
  componentWillUnmount() {
    if (this.resizeObserver) {
      this.resizeObserver.unobserve(document.getElementById('cms-demo-right'));
    }
  }

在网上查了下关于ResizeObserver API, 当我们需要知道一个元素的大小发生变化或者屏幕横屏时,我们需要监听 window.resize 事件或者 window.orientationchange方法。 由于 reize 事件会在一秒内触发将近60次,所以很容易在改变窗口大小时导致性能问题。换句话说,window.resize事件通常是浪费的,因为它会监听每个元素的大小变化(只有 window 对象才有 resize事件),而不是具体到某个元素的变化。如果我们只想监听某个元素的变化的话,这种操作就很浪费性能了。

而刚好,ResizeObserver API 就可以帮助我们: 监听一个 DOM 节点的变化,这种变化包括但不仅限于:

  1. 某个节点的出现和隐藏
  2. 某个节点的大小的变化

实际上, ResizeObserver API使用了观察者模式,也就是我们常说的发布- 订阅模式。 发布-订阅模式是 JavaScript 中典型的设计模式,

  • observe

语法:

const myObserver = new ResizeObserver(entries => {
  entries.forEach(entry => {
    console.log(`大小位置:${entry.contentRect}`);
    console.log(`监听的DOM:${entry.target}`);
  })
})
myObserver.observe(document.body)

以上,调用实例对象的observe方法,监听整个body 节点的变化,当改变窗口大小或者某个DOM 节点出现或隐藏时,就会触发回调。

  • unobserve unobserve 方法,顾名思义了,就是取消监听某个DOM节点。比如说想在两秒后取消监听document.body,那么这样做就好了
 componentWillUnmount() {
    if (this.resizeObserver) {
      this.resizeObserver.unobserve(document.getElementById('cms-demo-right'));
    }
  }