一.思路
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()