我终于发布了自己的第一个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();
  }
}
相关推荐
有梦想的咕噜6 分钟前
Electron 是一个用于构建跨平台桌面应用程序的开源框架
前端·javascript·electron
yqcoder8 分钟前
electron 监听窗口高端变化
前端·javascript·vue.js
Python私教24 分钟前
Flutter主题最佳实践
前端·javascript·flutter
GDAL43 分钟前
HTML入门教程7:HTML样式
前端·html
生命几十年3万天1 小时前
解决edge浏览器无法同步问题
前端·edge
杨荧1 小时前
【JAVA毕业设计】基于Vue和SpringBoot的校园美食分享平台
java·开发语言·前端·vue.js·spring boot·java-ee·美食
API199701081102 小时前
京东平台接口技术详解及示例代码
开发语言·前端·python
前端热爱者2 小时前
axios post请求body为字符串时的解决方法
开发语言·前端·javascript
Monly212 小时前
JS:JSON操作
前端·javascript·json
zerotower3 小时前
nextjs 构建自己的react组件库(一) - 项目的初始化配置和部署
前端·next.js