简单实现一个水印插件

一.思路

1.使用类实现,实例化后可以生成一个水印元素并挂载到某个元素上

2.该元素的样式需要计算,主要是背景图(datauri或者url)

3.拓展水印内容(使用canvas绘制的图片)

4.水印元素应该具备一些防修改能力,如修改style、class、id以及删除

二、实现

1.初始化部分

typescript 复制代码
export class Watermark {
  options = {} as WatermarkOptions;
  container: HTMLElement | null;
  watermark: HTMLElement | null;

  constructor(options?: WatermarkOptions) {
    this.options = JSON.parse(JSON.stringify(DEFAULT_OPTIONS));
    this.container = null;
    this.watermark = null;
    this.init(options)
  }

  init (options: WatermarkOptions = {}) {
    this.options = this._mergeOptions(options);

    if (this.watermark && this.container) {
      this.container.removeChild(this.watermark);
    }
    // 挂载容器支持dom、string或者不传
    this.container = options.container
      ? typeof options.container === 'string'
        ? document.querySelector(options.container) || document.body
        : options.container
      : document.body;

    this.watermark = document.createElement('div');
  }

  // 挂载
  mount () {
    if (!this.container || !this.watermark) {
      console.warn('容器元素或挂载元素不存在,挂在失败');
      return;
    }
    this.container.appendChild(this.watermark)
  }

  // 直接使用静态方法生成实例,多一份实例化api
  static mount(options?: WatermarkOptions) {
    const instance = new Watermark(options);
    instance.mount();
    return instance;
  }
  
  _mergeOptions (userOptions: WatermarkOptions = {}) {
    const isObject = (obj: any) => obj && typeof obj === 'object' && !Array.isArray(obj);

    const deepMerge = (target: any, source: any): any => {
      if (isObject(target) && isObject(source)) {
        Object.keys(source).forEach(key => {
          if (isObject(source[key])) {
            if (!target[key]) {
              target[key] = source[key];
            }
            deepMerge(target[key], source[key]);
          } else {
            target[key] = source[key];
          }
        });
      }
      return target;
    };

    return deepMerge(this.options, userOptions);
  }
}

2.样式计算

typescript 复制代码
get _mergedStyle () {
    const { style, image } = this.options;
    if (!style && !image) {
      return defaultStyle;
    }
    const imageStyle = image
      ? typeof image === 'string'
        ? { 'background-image': `url(${image})` }
        : this._renderImageStyle(image)
      : {};
    if (!style) {
      return Object.assign({}, defaultStyle, imageStyle);
    }
    if (typeof style === 'string') {
      const userStyles = style?.split(';').reduce((acc, styleItem) => {
        const [key, value] = styleItem.split(':').map(s => s.trim());
        if (key && value) {
          acc[key] = value;
        }
        return acc;
      }, {} as Record<string, string>);

      return Object.assign({}, defaultStyle, userStyles, imageStyle);
    } else {
      return Object.assign({}, defaultStyle, style, imageStyle);
    }
}

get _mergedStyleText () {
    const style = this._mergedStyle;
    return Object.keys(style).map(key => `${key}: ${style[key]}`).join(';');
}

3.支持canvas绘图

typescript 复制代码
_renderImageStyle (image: WatermarkCanvas) {
    const { watermarkText, watermarkFont, watermarkColor, watermarkOpacity, watermarkRotate, watermarkCanvasHeight, watermarkCanvasWidth } =image
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    if (ctx) {
      const width = watermarkCanvasWidth || 200;
      const height = watermarkCanvasHeight || 200;
      canvas.width = width;
      canvas.height = height;

      ctx.fillStyle = watermarkColor || 'rgba(0, 0, 0, 0.3)';
      ctx.font = watermarkFont || '16px sans-serif';
      ctx.globalAlpha = watermarkOpacity || 0.5;
      ctx.rotate((watermarkRotate || -30) * Math.PI / 180);
      ctx.fillText(watermarkText, 0, height / 2);

      const dataURI = canvas.toDataURL();
      canvas.remove();
      return { 'background-image': `url(${dataURI})` };
    } else {
      canvas.remove();
      return {};
    }
}

4.防修改

需要在初始化时进行监控,并在销毁时释放,此处省了

typescript 复制代码
_styleObserve () {
    if (!this.watermark) return null;
    const self = this;
    const observer = new MutationObserver(function(mutations) {
      observer.disconnect();
      mutations.forEach(function(mutation) {
        if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
          self.watermark!.style.cssText = self._mergedStyleText;
        }
        if (mutation.attributeName === 'class') {
          self.watermark!.className = '';
        }
        if (mutation.attributeName === 'id') {
          self.watermark!.id = '';
        }
      });
      observer.observe(self.watermark!, config);
    });
    // 配置观察选项
    const config = {
      attributes: true,
      attributeFilter: ['style', 'class', 'id'],
    };
    // 开始观察目标节点
    observer.observe(this.watermark, config);
    return observer;
}

_removeObserve() {
    if (!this.container) return null;
    const self = this;
    const observer = new MutationObserver(function(mutations) {
      observer.disconnect();
      mutations.forEach(function(mutation) {
        if (mutation.type === 'childList' && !self.container!.contains(self.watermark)) {
          self.mount();
        }
      });
      observer.observe(self.container!, config);
    });
    // 配置观察选项
    const config = {
      childList: true, // 监听子节点变化
      subtree: false
    };
    // 开始观察目标节点
    observer.observe(this.container, config);
    return observer;
}

三、使用

安装

bash 复制代码
npm i @irises/watermark

使用

typescript 复制代码
import { Watermark } from '@irises/watermark';

const instance1 = Watermark.mount({ container: '#app' });
// 或者
const instance2 = new Watermark({ container: '#app' });
instance2.mount();

// 销毁
instance1.unmount()
instance2.unmount()
相关推荐
专注API从业者5 小时前
Python + 淘宝 API 开发:自动化采集商品数据的完整流程
大数据·运维·前端·数据挖掘·自动化
烛阴5 小时前
TypeScript高手密技:解密类型断言、非空断言与 `const` 断言
前端·javascript·typescript
样子20186 小时前
Uniapp 之renderjs解决swiper+多个video卡顿问题
前端·javascript·css·uni-app·html
Nicholas686 小时前
flutterAppBar之SystemUiOverlayStyle源码解析(一)
前端
黑客飓风7 小时前
JavaScript 性能优化实战大纲
前端·javascript·性能优化
emojiwoo8 小时前
【前端基础知识系列六】React 项目基本框架及常见文件夹作用总结(图文版)
前端·react.js·前端框架
张人玉8 小时前
XML 序列化与操作详解笔记
xml·前端·笔记
杨荧9 小时前
基于Python的宠物服务管理系统 Python+Django+Vue.js
大数据·前端·vue.js·爬虫·python·信息可视化
YeeWang9 小时前
🎉 Eficy 让你的 Cherry Studio 直接生成可预览的 React 页面
前端·javascript
gnip9 小时前
Jenkins部署前端项目实战方案
前端·javascript·架构