我终于发布了自己的第一个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();
  }
}
相关推荐
拉不动的猪5 分钟前
前端常见数组分析
前端·javascript·面试
小吕学编程21 分钟前
ES练习册
java·前端·elasticsearch
Asthenia041229 分钟前
Netty编解码器详解与实战
前端
袁煦丞33 分钟前
每天省2小时!这个网盘神器让我告别云存储混乱(附内网穿透神操作)
前端·程序员·远程工作
一个专注写代码的程序媛2 小时前
vue组件间通信
前端·javascript·vue.js
一笑code2 小时前
美团社招一面
前端·javascript·vue.js
懒懒是个程序员2 小时前
layui时间范围
前端·javascript·layui
NoneCoder2 小时前
HTML响应式网页设计与跨平台适配
前端·html
凯哥19702 小时前
在 Uni-app 做的后台中使用 Howler.js 实现强大的音频播放功能
前端
烛阴2 小时前
面试必考!一招教你区分JavaScript静态函数和普通函数,快收藏!
前端·javascript