如何实现一个React水印组件

9,796 阅读4分钟

基本介绍 📝

  • 前阵子通过 前端水印生成方案(网页水印+图片水印) 这篇文章学习了一下水印的生成方案,对其中使用 Canvas 实现网页水印的方案十分感兴趣,于是对相关代码加以修改,实现了一个 React 水印组件并发布到 👉 npm
  • 该组件是通过 Canvas 生成水印,并使用 MutationObserve (可以监听DOM结构变化的接口)监视 DOM 的变动,使得水印不可被删除、且属性不可被修改。
  • 关于发布react组件的方法之前已在使用npm发布一个react组件(踩坑实践)中进行介绍,本文将不再进行阐述,本文着重介绍组件的使用效果及具体实现。

效果演示 🤩

  • 该组件已发布到npm上,可使用以下命令安装:
npm install --save watermark-component-for-react
  • 在项目中使用:
import React from 'react';
import WaterMark from 'watermark-component-for-react';
import * as styles from './index.css';

class ReactDemo extends React.Component{
  render () {
    const content = `内部文档,请勿外传 by-前端小黑`;
    return (
      <WaterMark content={content}>
        <div className={styles.wrapper}>hello world</div>
      </WaterMark>
    );
  }
}
export default ReactDemo;
  • 结果:
  • ⚠️注意:需要加水印的组件需要包裹在 WaterMark 组件中,即作为 WaterMark 的子组件。

组件特点

  1. 使用 MutationObserve 监视 DOM 的变动,水印不可被删除、且属性不可被修改:

  2. 丰富的 props 使得水印组件可实现定制化需求:

成员 说明 类型 默认值
style watermark最外层组件内联样式 undefined | object undefined
container 容器 HTMLDivElement document.body
width canvas元素宽 string 300
height canvas元素高 string 200
textAlign 绘制文本的对齐方式 string left
textBaseline 文本基准线 string bottom
font 字体大小及样式 string 16px Microsoft Yahei
fillStyle 自定义水印的颜色 string black
content 水印内容 string 内部文档,请勿外传
globalAlpha 设置图形和图像透明度的值 number 0.1
rotate 文字旋转角度 number 16
zIndex 元素堆叠顺序 number 1000

具体实现 🧐

  • 水印组件的源码如下:
import * as React from 'react';
import { watermark } from '../../utils/lib'

export default class WaterMark extends React.Component {
  constructor(props) {
    super(props);

    this.container = null;
  }

  componentDidMount () {
    const { style, ...options } = this.props;
    watermark({
      container: this.container,
      ...options,
    });
  }

  render () {
    const style = {
      position: 'relative',
      ...this.props.style,
    };
    return (
      <div ref={(el) => this.container = el} id="watermark" style={style}>
        {this.props.children}
      </div>
    );
  }
}
  • 核心代码是 watermark 这个方法,具体代码如下:
export function watermark (options) {
  const {
    container = document.body, // 容器
    width = '300', // canvas元素宽
    height = '200', // canvas元素高
    textAlign = 'left', // 文字对齐
    textBaseline = 'bottom', // 基准线
    font = '16px Microsoft Yahei', // 字体大小及样式
    fillStyle = '#000', // 自定义水印的颜色
    content = '内部文档,请勿外传', // 水印内容
    globalAlpha = 0.1, // 设置图形和图像透明度的值
    rotate = 16, // 文字旋转角度
    zIndex = 1000, // 元素堆叠顺序
  } = options;

  let canvas = document.createElement('canvas');
  canvas.setAttribute('width', width);
  canvas.setAttribute('height', height);
  let ctx = canvas.getContext('2d'); // 获取 canvas2d 上下文
  ctx.globalAlpha = globalAlpha;
  ctx.textAlign = textAlign;
  ctx.textBaseline = textBaseline;
  ctx.font = font;
  ctx.fillStyle = fillStyle;
  ctx.rotate((Math.PI * rotate) / 180);
  ctx.fillText(content, 50, 50);

  const base64Url = canvas.toDataURL(); // 返回一个包含图片展示的 data URI

  const __wm = document.querySelector('.__wm');//选择器
  const watermarkDiv = __wm || document.createElement("div");
  const styleStr = `
    position:absolute;
    top:0px;
    left:0px;
    width:100%;
    height:100%;
    z-index:${zIndex};
    pointer-events:none;
    background-repeat:repeat;
    background-image:url('${base64Url}')`;

  watermarkDiv.setAttribute('style', styleStr);
  watermarkDiv.classList.add('__wm'); // 为元素添加“__wm”类名

  container.style.position = 'relative';
  if (!__wm) {
    container.appendChild(watermarkDiv); // 添加元素
  }

  const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
  // 检查浏览器是否支持这个API
  if (MutationObserver) {
    const args = arguments[0];
    let mo = new MutationObserver(function () {
      const __wm = document.querySelector('.__wm');
      // 只在__wm元素变动才重新调用
      if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm || container.style.position !== 'relative') {
        // 避免一直触发
        mo.disconnect();
        mo = null;
        watermark(args);
      }
    });
    mo.observe(container, {
      attributes: true, // 观察目标节点的属性节点
      subtree: true, // 观察目标节点的所有后代节点
      childList: true, // 观察目标节点的子节点
    })
  }
};
  • 为便于理解,已在以上文件加上了注释,可以看到,通过使用MutationObserve ,当水印组件被删除、属性被修改或水印组件的容器定位属性 position 不为 relative时,会重新调用watermark方法。

总结 👀

  • 为了避免公司的内部文档被截图外泄,有必要在系统页面上面增加水印。
  • 为了避免某些用户将水印删除,需要对水印组件进行监听(通过 MutationObserve 实现),当组件被删除时动态插入新的水印。
  • 本文介绍的水印组件(watermark-component-for-react)已发布到 NPM,欢迎大家下载使用。

以上内容如有遗漏错误,欢迎留言 ✍️指出,一起进步💪💪💪

如果觉得本文对你有帮助,🏀🏀留下你宝贵的 👍

参考资料

  1. 前端水印生成方案(网页水印+图片水印)
  2. Canvas API MDN文档
  3. MutationObserver MDN文档
  4. 使用npm发布一个react组件(踩坑实践)