UNPKG

@rain688/watermark

Version:
242 lines (224 loc) 6.04 kB
type BaseOptions = { content: string | HTMLImageElement /** * 水印旋转角度,单位为度 */ rotate?: number /** * 水印字体 */ font?: string /** * 水印颜色 */ color?: string /** * 水印宽度 */ width?: number /** * 水印高度 */ height?: number /** * x轴间距 */ xGap?: number /** * y轴间距 */ yGap?: number /** * 是否在窗口大小改变时重新渲染 */ resizeRender?: boolean imgInfo?: { width: number; height: number; opacity?: number } } export class Watermark<T extends string | HTMLImageElement = string> { canvas: HTMLCanvasElement #parentElement?: HTMLElement options: Required<BaseOptions> & { imgInfo?: { width: number; height: number } } context?: CanvasRenderingContext2D resizeObserver?: ResizeObserver constructor(canvas: HTMLCanvasElement, options: BaseOptions) { this.canvas = canvas if (canvas.parentElement) { this.#parentElement = canvas.parentElement } const { content, rotate = 45, font = '20px Arial', color = '#000', xGap = 50, yGap = 50, resizeRender = true, imgInfo = { width: 0, height: 0, opacity: 1, }, } = options if (content instanceof HTMLImageElement) { if (!imgInfo.width || !imgInfo.height) { const { width, height } = content.getBoundingClientRect() imgInfo.width = width imgInfo.height = height } } let { width, height } = options if (!width || !height) { const { width: oWidth, height: oHeight } = canvas.getBoundingClientRect() width = width || oWidth height = height || oHeight canvas.width = width canvas.height = height } this.options = { content: content as T, rotate, font, color, width, height, xGap, yGap, resizeRender, imgInfo, } const context = canvas.getContext('2d') if (context) { this.context = context this.init() } else { throw new Error('context is null') } } init() { this.draw() if (this.options.resizeRender) { this.#startResizeObserver() } this.#startMutationObserver() } #startResizeObserver() { this.resizeObserver = new ResizeObserver((values) => { const value = values[0] if (value) { const { width, height } = value.contentRect this.canvas.width = width this.canvas.height = height this.context!.clearRect(0, 0, width, height) this.draw() } }) this.resizeObserver.observe(this.canvas) } #startMutationObserver() { const callback: MutationCallback = (mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'childList') { const { addedNodes, removedNodes, target } = mutation if (target === this.canvas) { console.log(mutation) addedNodes.forEach((item) => (item as Element).remove()) } if (target === this.#parentElement) { removedNodes.forEach((item) => { if (item === this.canvas) { this.#parentElement?.appendChild(this.canvas) } }) } } } } const observer = new MutationObserver(callback) observer.observe(this.canvas, { attributes: false, childList: true, subtree: false }) if (this.#parentElement) { observer.observe(this.#parentElement, { attributes: false, childList: true, subtree: false }) } } clearResizeObserver() { if (this.resizeObserver) { this.resizeObserver.disconnect() this.resizeObserver = undefined } } draw() { const { content } = this.options if (content instanceof HTMLImageElement) { this.#drowImage() } else { /** * 水印内容 */ this.#drowText() } } #drowText() { const { content, rotate, font, color, width, height, xGap, yGap } = this.options this.context!.font = font this.context!.fillStyle = color for (let i = 0; i <= Math.ceil(width / xGap) + 1; i++) { for (let k = 0; k <= Math.ceil(height / yGap) + 1; k++) { this.#drawTextItem(this.context!, { content: content as string, rotate, x: i * xGap, y: k * yGap, }) } } this.context!.beginPath() } #drowImage() { const { content, rotate, width, height, xGap, yGap, imgInfo } = this.options this.context!.globalAlpha = imgInfo.opacity ?? 1 for (let i = 0; i <= Math.ceil(width / xGap) + 1; i++) { for (let k = 0; k <= Math.ceil(height / yGap) + 1; k++) { this.#drawImageItem(this.context!, { content: content as HTMLImageElement, rotate, x: i * xGap, y: k * yGap, imgInfo, }) } } this.context!.globalAlpha = 1 } #drawImageItem = ( context: CanvasRenderingContext2D, options: { content: HTMLImageElement x: number y: number rotate: number imgInfo: { width: number; height: number } }, ) => { const { content, x, y, rotate, imgInfo } = options context.save() // 保存当前状态 context.translate(x, y) // 移动到指定位置 context.rotate((rotate * Math.PI) / 180) // 旋转画布 context.drawImage(content, 0, 0, imgInfo.width, imgInfo.height) // 绘制图片 context.restore() // 恢复到保存的状态 } #drawTextItem = ( context: CanvasRenderingContext2D, options: { content: string x: number y: number rotate: number }, ) => { const { content, x, y, rotate } = options context.save() // 保存当前状态 context.translate(x, y) // 移动到指定位置 context.rotate((rotate * Math.PI) / 180) // 旋转画布 context.fillText(content, 0, 0) // 绘制文字 context.restore() // 恢复到保存的状态 } }