专业的编程技术博客社区

网站首页 > 博客文章 正文

手把手教你如何实现一个React水印组件「实践」

baijin 2024-11-25 09:38:13 博客文章 3 ℃ 0 评论


作者:前端小黑

原文地址:https://juejin.im/post/5e75e2b16fb9a07cc10ac552

基本介绍

  • 前阵子通过 前端水印生成方案(网页水印+图片水印) 这篇文章学习了一下水印的生成方案,对其中使用 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;
复制代码
  • 结果:img
  • ??注意:需要加水印的组件需要包裹在 WaterMark 组件中,即作为 WaterMark 的子组件。

组件特点

  1. 使用 MutationObserve 监视 DOM 的变动,水印不可被删除、且属性不可被修改:img
  2. 丰富的 props 使得水印组件可实现定制化需求:

成员说明类型默认值stylewatermark最外层组件内联样式undefined | objectundefinedcontainer容器HTMLDivElementdocument.bodywidthcanvas元素宽string300heightcanvas元素高string200textAlign绘制文本的对齐方式stringlefttextBaseline文本基准线stringbottomfont字体大小及样式string16px Microsoft YaheifillStyle自定义水印的颜色stringblackcontent水印内容string内部文档,请勿外传globalAlpha设置图形和图像透明度的值number0.1rotate文字旋转角度number16zIndex元素堆叠顺序number1000

具体实现

  • 水印组件的源码如下:
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,欢迎大家下载使用。

?

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

?

?

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

推荐React 学习相关文章

「实践」React 中必会的 10 个概念

「干货」深入浅出React组件逻辑复用的那些事儿

手把手教你从Mixin深入到HOC再到Hook【React】

深入Facebook 官方React 状态管理器Recoil讲解

手把手教你实践搭建React组件库「超详细」

在 React 中自动复制文本到剪贴板「实践」

「干货满满」从零实现 react-redux

深入详解大佬用33行代码实现了React

让你的 React 组件性能跑得再快一点「实践」

React源码分析与实现(三):实践 DOM Diff

React源码分析与实现(一):组件的初始化与渲染「实践篇」

React源码分析与实现(二):状态、属性更新->setState「实践篇」

细说React 核心设计中的闪光点

手把手教你10个案例理解React hooks的渲染逻辑「实践」

React-Redux 100行代码简易版探究原理

手把手深入教你5个技巧编写更好的React代码【实践】

React 函数式组件性能优化知识点指南汇总

13个精选的React JS框架

深入浅出画图讲解React Diff原理【实践】

【React深入】React事件机制

Vue 3.0 Beta 和React 开发者分别杠上了

手把手深入Redux react-redux中间件设计及原理(上)【实践】

手把手深入Redux react-redux中间件设计及原理(下)【实践】

前端框架用vue还是react?清晰对比两者差异

为了学好 React Hooks, 我解析了 Vue Composition API

【React 高级进阶】探索 store 设计、从零实现 react-redux

写React Hooks前必读

深入浅出掌握React 与 React Native这两个框架

可靠React组件设计的7个准则之SRP

React Router v6 新特性及迁移指南

用React Hooks做一个搜索栏

你需要的 React + TypeScript 50 条规范和经验

手把手教你绕开React useEffect的陷阱

浅析 React / Vue 跨端渲染原理与实现

React 开发必须知道的 34 个技巧【近1W字】

三张图详细解说React组件的生命周期

手把手教你深入浅出实现Vue3 & React Hooks新UI Modal弹窗

手把手教你搭建一个React TS 项目模板

全平台(Vue/React/微信小程序)任意角度旋图片裁剪组件

40行代码把Vue3的响应式集成进React做状态管理

手把手教你深入浅出React 迷惑的问题点【完整版】

作者:前端小黑

原文地址:https://juejin.im/post/5e75e2b16fb9a07cc10ac552

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表