实现水印组件
水印组件要达到的目标
- 要能完全覆盖目标节点
- 要能防止水印被手动删除
- 打印预览也要有水印,不能被手动去掉
- 支持清除
实现思路
- 创建一个 canvas 节点
- 在 canvas 节点中画一个水印
- 将 canvavs 中画的水印转换为 base64 图片
- 创建一个 div 节点,并添加到需要添加的水印的节点之下
- 将这个创建的 div 节点设置为绝对定位或 fixed 定位(根据你的实际情况来)
- 将 top,left,right,bottom 都设置为 0,使这个 div 完全覆盖他的父节点
- 将新建的这个 div 的背景设置为 base64 图片,并设置 repeat,使得背景图片能够以循环的方式覆盖整个 div
- 设置 css 样式
color-adjust:exact,让打印预览的时候,水印也不会消失
实现代码
ts
function clear(watermarkDomId: string, observer: MutationObserver) {
observer && observer.disconnect()
const watermarkDom = document.getElementById(watermarkDomId)
if (watermarkDom && watermarkDom.parentElement) {
// @ts-ignore
watermarkDom.disconnectObs && watermarkDom.disconnectObs()
watermarkDom.parentElement.removeChild(watermarkDom)
}
}
/**
* 创建水印
*
* @param watermarkDomId 水印id
* @param options 水印配置
* @param observer 观察器实例,用于监听水印节点是否被删除
*/
function create(watermarkDomId: string, options: WatermarkOptions, observer: MutationObserver) {
const { height, width, text, el } = options
const elDom = typeof el === 'string' ? (document.querySelector(el) as HTMLElement) : el
if (!elDom) return
const canvasDom: HTMLCanvasElement = Object.assign(document.createElement('canvas'), { width, height })
const canvasRender2D = canvasDom.getContext('2d')
if (!canvasRender2D) return
canvasRender2D.rotate((Math.PI / 120) * -20)
canvasRender2D.fillStyle = 'rgba(0, 0, 0, 0.3)'
canvasRender2D.font = '16px Microsoft Yahei'
canvasRender2D.textAlign = 'center'
canvasRender2D.textBaseline = 'middle'
canvasRender2D.fillText(text, width / 10, height / 1.5)
// 将canvas转换为base64编码图片
const base64 = canvasDom.toDataURL('image/png')
// 创建水印节点
const watermarkDom = document.createElement('div')
// 将水印节点作为目标节点的子节点
elDom.appendChild(watermarkDom)
watermarkDom.id = watermarkDomId
observer.observe(elDom, {
childList: true,
})
Object.assign(watermarkDom.style, {
background: `url(${base64}) left top repeat`,
'color-adjust': 'exact',
'print-color-adjust': 'exact',
'-webkit-print-color-adjust': 'exact',
position: 'absolute',
pointerEvents: 'none',
top: 0,
right: 0,
left: 0,
bottom: 0,
display: 'block',
visibility: 'visible',
// 防止水印被其他元素覆盖
'z-index': 999,
})
const obs = new MutationObserver(() => {
Object.assign(watermarkDom.style, {
background: `url(${base64}) left top repeat`,
'color-adjust': 'exact',
'print-color-adjust': 'exact',
'-webkit-print-color-adjust': 'exact',
position: 'absolute',
pointerEvents: 'none',
top: 0,
right: 0,
left: 0,
bottom: 0,
display: 'block',
visibility: 'visible',
// 防止水印被其他元素覆盖
'z-index': 999,
})
})
// @ts-ignore
watermarkDom.disconnectObs = () => {
obs.disconnect()
}
obs.observe(watermarkDom, { attributes: true, attributeFilter: ['style'] })
}
export interface WatermarkOptions {
/**
* 水印文本
*/
text: string
/**
* canvas的宽。宽度应该大于等于水印文本的宽度
*/
width: number
/**
* canvas的高。高度应该大于等于水印文本的高度
*/
height: number
/**
* 要添加谁赢的html元素或html选择器
*/
el: HTMLElement | string
}
export function watermarkBuilder(options: WatermarkOptions) {
// 水印节点的id
const watermarkDomId = `water-markdom-id-${new Date().getTime()}`
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
for (let i = 0; i < mutation.removedNodes.length; i++) {
const ele = mutation.removedNodes.item(i) as HTMLElement
if (ele && ele.id === watermarkDomId) {
observer.disconnect()
//@ts-ignore
if (ele.disconnectObs) {
// 如果水印节点被删除,则同时去除监听
//@ts-ignore
ele.disconnectObs()
}
create(watermarkDomId, options, observer)
}
}
})
})
return {
build: () => create(watermarkDomId, options, observer),
clear: () => clear(watermarkDomId, observer),
}
}
调用示例
ts
import { watermarkBuilder } from '@/utils/watermarker'
let builder = watermarkBuilder({
text: '你好张三',
width: 100,
height: 100,
el: document.body,
})
function clearWatermark() {
builder.clear()
}
const pageContentRef = ref()
function addWatermark(type: number) {
builder.clear()
if (type === 1) {
builder = watermarkBuilder({
text: '你好张三',
width: 100,
height: 100,
el: document.body,
})
builder.build()
} else {
builder = watermarkBuilder({
text: '你好张三',
width: 100,
height: 100,
el: pageContentRef.value!.$el,
})
builder.build()
}
}