我终于发布了自己的第一个npm包——前端添加水印(dc-water-mark)

说来惭愧两年的前端了,才发布了自己的第一个npm包。

前几天项目上有一个添加公司水印的需求。

寻思着封装起来吧

使用流程

自己封装了一个npm包,使用方式如下:

js 复制代码
npm i dc-water-mark

引入依赖后,实例watermark对象即可:

ts 复制代码
export interface Params {
    container: HTMLElement, // 加水印的节点
    width: number, // 单个水印的宽度
    height: number, // 单个水印的高度
    fontSize: number, // 字体大小
    font: string, // 字体
    color: string, // 字体颜色
    content: string, // 水印内容
    rotate: number, // 旋转角度
    zIndex?: number, // 水印层次
    opacity: number, // 水印透明度
    x: number, // 水印位置
    y: number, // 水印位置
    onWatermarkChanged: Function // 水印修改回掉函数
}

const watermark = new Watermark({
    container: 'dom节点',
    content: `watermark`,
    x: 100,
    y: 100,
    color: 'red',
    opacity: '0.5',
    zIndex: 1000,
    rotate: -30,
    fontSize: 20,
    width: 250,
    height: 150,
});
watermark.output();

// 不用的时候销毁
watermark.destroy();

效果图:

github仓库地址,欢迎大家star github.com/duchao-duch...

实现原理

1、使用canvas绘制水印,将canvas设置为一个节点的背景

2、将创建的节点insertBefore到要添加水印的节点。

3、使用MutationObserver监听节点变化防止被篡改。

核心源码如下(复制过去就能用):

ts 复制代码
import { isNullOrUndefined, isFunction, isDom } from './base';
import { Params } from './type';

export default class Watermark {
  params: Params;
  styleStr: string;
  containerObserver: MutationObserver;
  observer: MutationObserver;
  watermarkDiv: HTMLElement;
  flag: boolean;

  constructor(params: Params) {
    this.params = Object.assign(
      {
        container: document.body,
        width: 250,
        height: 150,
        fontSize: 16,
        font: 'microsoft yahei',
        color: '#cccccc',
        content: 'watermark',
        rotate: -30,
        zIndex: 1000,
        opacity: 0.5,
      },
      params,
    );

    // 水印dom样式
    this.styleStr = `
    position:absolute;
    top:0;
    left:0;
    width:100%;
    height:100%;
    z-index:${this.params.zIndex};
    pointer-events:none;
    background-repeat:repeat;
    background-image:url('${this.toDataURL()}')`;

    this.params.x = isNullOrUndefined(params.x) ? this.params.width / 2 : params.x;
    this.params.y = isNullOrUndefined(params.y) ? this.params.height / 2 : params.y;
    this.containerObserver = new MutationObserver((mutationsList, observer) => {
      // 当观察到变动时执行的回调函数
      mutationsList.forEach((mutation) => {
        const watermarkDom = document.getElementsByClassName('open-watermark')[0];
        if (!watermarkDom) {
          // 水印dom被删除,重新创建
          this.createWatermarkDom();
          if (isFunction(this.params.onWatermarkChanged)) {
            // 水印dom被修改时,执行传入的回调
            this.params.onWatermarkChanged(mutation, observer);
          }
        }
      });
    });
    this.observer = new MutationObserver((mutationsList, observer) => {
      // 当观察到变动时执行的回调函数
      mutationsList.forEach((mutation) => {
        const watermarkDom = document.getElementsByClassName('open-watermark')[0];
        if (watermarkDom?.getAttribute('style') !== this.styleStr) {
          // 水印dom样式被修改
          watermarkDom.setAttribute('style', this.styleStr);
          if (isFunction(this.params.onWatermarkChanged)) {
            this.params.onWatermarkChanged(mutation, observer);
          }
        }
      });
    });
  }

  toDataURL() {
    const { width, height, fontSize, font, color, rotate, content, opacity, x, y } = this.params;
    const canvas = document.createElement('canvas');
    canvas.setAttribute('width', `${width}px`);
    canvas.setAttribute('height', `${height}px`);

    const ctx = canvas.getContext('2d');
    if (ctx) {
      ctx.clearRect(0, 0, width, height);
      ctx.textBaseline = 'top';
      ctx.textAlign = 'left';
      ctx.fillStyle = color;
      ctx.globalAlpha = opacity;
      ctx.font = `${fontSize}px ${font}`;
      ctx.translate(x, y);
      ctx.rotate((Math.PI / 180) * rotate);
      ctx.translate(-x, -y - fontSize);
      ctx.fillText(content, x, y + fontSize);
    }

    return canvas.toDataURL();
  }

  // 创建水印dom
  createWatermarkDom() {
    const watermarkDom = document.getElementsByClassName('open-watermark')[0];
    const { container } = this.params;
    if (isDom(container) && !watermarkDom) {
      this.watermarkDiv = document.createElement('div');
      this.watermarkDiv.setAttribute('style', this.styleStr);
      this.watermarkDiv.setAttribute('class', 'open-watermark');
      container.style.position = 'relative';
      container.insertBefore(this.watermarkDiv, container.firstChild);
      this.observer.observe(this.watermarkDiv, {
        attributes: true, // 观察节点属性改变
        childList: true, // 观察子节点改变
        subtree: true, // 观察所有后代节点的childLIst、attributes变化
      });
    }
  }

  output() {
    this.flag = true;
    this.createWatermarkDom();
    // 观察元素
    this.containerObserver.observe(this.params.container, {
      attributes: true,
      childList: true,
      characterData: true,
    });
  }

  destroy() {
    if (!this.watermarkDiv) return;
    this.watermarkDiv.remove();
    this.observer.disconnect();
    this.containerObserver.disconnect();
  }
}
相关推荐
Sunlightʊə2 小时前
2.登录页测试用例
运维·服务器·前端·功能测试·单元测试
Code Crafter3 小时前
ES6-ES14 新特性速查
前端·ecmascript·es6
Lhuu(重开版3 小时前
CSS从0到1
前端·css·tensorflow
不说别的就是很菜4 小时前
【前端面试】HTML篇
前端·html
前端一小卒4 小时前
生产环境Sourcemap策略:从苹果事故看前端构建安全架构设计
前端·javascript
im_AMBER4 小时前
React 18
前端·javascript·笔记·学习·react.js·前端框架
老前端的功夫4 小时前
Vue2中key的深度解析:Diff算法的性能优化之道
前端·javascript·vue.js·算法·性能优化
han_5 小时前
前端高频面试题之Vue(高级篇)
前端·vue.js·面试
不说别的就是很菜5 小时前
【前端面试】CSS篇
前端·css·面试
by__csdn6 小时前
nvm安装部分node版本后没有npm的问题(14及以下版本)
前端·npm·node.js