出现水印
出现水印在 div 标签上
相同点:指令的应用场景,原理是一致的;
不同点:生命周期钩子函数名,指令定义的格式不一样。
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
// app是Vue实例。 e.g: app = createApp()
app.directive('指令名', {
// 省略其他生命周期钩子函数
// el: 使用了指令的dom
// binding.value: v-指令名="binding.value就是这里表达式的值"
mounted (el, binding) {
console.log(el, binding.value)
}
})
<元素 v-指令名="xxx" />
// 定义全局指令
export default {
install (app) {
// app是Vue实例。 e.g: app = createApp()
app.directive('指令名', {
// 省略其他生命周期钩子函数
// el: 使用了指令的dom
// binding.value: v-指令名="binding.value就是这里表达式的值"
mounted (el, binding) {
console.log(el, binding.value)
}
})
}
}
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'
import directives from './directives'
createApp(App).use(router).use(directives).mount('#app')
Canvas和MutationObserver
的实现方式toDataURL(type, encoderOptions),接收两个参数:
type:图片类型,比如image/png、image/jpeg、image/webp等等,默认为image/png格式 encoderOptions:图片质量的取值范围(0-1),默认值为0.92,当超出界限按默认值0.92
// 全局保存 canvas 和 div ,避免重复创建(单例模式)
const globalCanvas = null;
const globalWaterMark = null;
// 返回一个包含图片展示的 数据URL
const getDataUrl = (binding: any) => {
const rotate = -20;
const canvas = globalCanvas || document.createElement("canvas");
const ctx = canvas.getContext("2d"); // 获取canvas画布的绘图环境
ctx?.rotate((rotate * Math.PI) / 180); // 水印旋转角度
ctx.font = binding.font;
ctx.fillStyle = binding.fillStyle;
ctx?.fillText(binding.text || '机密文件', canvas.width / 3, canvas.height / 2);
return canvas.toDataURL("image/png");
};
MutationObserver 接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。
MutationObserver() 创建并返回一个新的 MutationObserver 它会在指定的 DOM 发生变化时被调用。
阻止 MutationObserver 实例继续接收的通知,直到再次调用其 observe() 方法,该观察者对象包含的回调函数都不会再被调用。
配置 MutationObserver 在 DOM 更改匹配给定选项时,通过其回调函数开始接收通知。
从 MutationObserver 的通知队列中删除所有待处理的通知,并将它们返回到 MutationRecord 对象的新 Array 中。
(1)直接删除dom
(2)删除style中的属性
// watermark 样式
let style = `
display: block;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-repeat: repeat;
pointer-events: none;`;
// 定义指令配置项
const directives: any = {
mounted(el: HTMLElement, binding: any) {
// 注意img有onload的方法,如果自定义指令注册在html标签的话,只需要init(el, binding.value)
el.onload = init.bind(null, el, binding);
// init(el, binding);
},
};
// 初始化
const init = (el: HTMLElement, binding: any = {}) => {
// 设置水印
setWaterMark(el, binding.value);
// 启动监控
createObserver(el, binding.value);
};
// 设置水印
const setWaterMark = (el: HTMLElement, binding: any = {}) => {
const { parentElement } = el;
// 获取对应的 canvas 画布相关的 base64 url
const url = getDataUrl(binding);
// 创建 waterMark 父元素
const waterMark = globalWaterMark || document.createElement("div");
waterMark.className = `water-mark`; // 方便自定义展示结果
style = `${style}background-image: url(${url});`;
waterMark.setAttribute("style", style);
// 将对应图片的父容器作为定位元素
parentElement?.setAttribute("style", "position: relative;");
// 将图片元素移动到 waterMark 中
parentElement?.appendChild(waterMark);
};
/**
* 监听 DOM 变化
* 用 MutationObserver 对水印元素进行监听,删除时,我们再立即生成一个水印元素就可以了
* @param el
* @param binding
*/
const createObserver = (el: HTMLElement, binding: any) => {
const waterMarkEl = el.parentElement?.querySelector(".water-mark");
const observer = new MutationObserver((mutationsList) => {
console.log('mutationsList', mutationsList)
if (mutationsList.length) {
const { removedNodes, type, target } = mutationsList[0];
const currStyle = waterMarkEl?.getAttribute("style");
// 证明被删除了
if (removedNodes[0] === waterMarkEl) {
// 停止观察。调用该方法后,DOM 再发生变动,也不会触发观察器
observer.disconnect();
// 初始化(设置水印,启动监控)
init(el, binding);
} else if (
type === "attributes" &&
target === waterMarkEl &&
currStyle !== style
) {
waterMarkEl.setAttribute("style", style);
}
}
});
observer.observe(el.parentElement, {
childList: true,
attributes: true,
subtree: true,
});
};
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'
import directives from './directives'
createApp(App).use(router).use(directives).mount('#app')
/directives/index.ts
import type { App } from 'vue'
import watermark from './waterMark'
export default function installDirective(app: App) {
app.directive(watermark.name, watermark.directives);
}
/directives/waterMark.ts
// 全局保存 canvas 和 div ,避免重复创建(单例模式)
const globalCanvas = null;
const globalWaterMark = null;
// 返回一个包含图片展示的 数据URL
const getDataUrl = (binding: any) => {
const rotate = -20;
const canvas = globalCanvas || document.createElement("canvas");
const ctx = canvas.getContext("2d"); // 获取canvas画布的绘图环境
ctx?.rotate((rotate * Math.PI) / 180); // 水印旋转角度
ctx.font = binding.font;
ctx.fillStyle = binding.fillStyle;
ctx?.fillText(binding.text || '机密文件', canvas.width / 3, canvas.height / 2);
return canvas.toDataURL("image/png");
};
// watermark 样式
let style = `
display: block;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-repeat: repeat;
pointer-events: none;`;
// 定义指令配置项
const directives: any = {
mounted(el: HTMLElement, binding: any) {
// 注意img有onload的方法,如果自定义指令注册在html标签的话,只需要init(el, binding.value)
// el.onload = init.bind(null, el, binding);
init(el, binding);
},
};
// 初始化
const init = (el: HTMLElement, binding: any = {}) => {
// 设置水印
setWaterMark(el, binding.value);
// 启动监控
createObserver(el, binding.value);
};
// 设置水印
const setWaterMark = (el: HTMLElement, binding: any = {}) => {
const { parentElement } = el;
// 获取对应的 canvas 画布相关的 base64 url
const url = getDataUrl(binding);
// 创建 waterMark 父元素
const waterMark = globalWaterMark || document.createElement("div");
waterMark.className = `water-mark`; // 方便自定义展示结果
style = `${style}background-image: url(${url});`;
waterMark.setAttribute("style", style);
// 将对应图片的父容器作为定位元素
parentElement?.setAttribute("style", "position: relative;");
// 将图片元素移动到 waterMark 中
parentElement?.appendChild(waterMark);
};
/**
* 监听 DOM 变化
* 用 MutationObserver 对水印元素进行监听,删除时,我们再立即生成一个水印元素就可以了
* @param el
* @param binding
*/
const createObserver = (el: HTMLElement, binding: any) => {
const waterMarkEl = el.parentElement?.querySelector(".water-mark");
const observer = new MutationObserver((mutationsList) => {
console.log('mutationsList', mutationsList)
if (mutationsList.length) {
const { removedNodes, type, target } = mutationsList[0];
const currStyle = waterMarkEl?.getAttribute("style");
// 证明被删除了
if (removedNodes[0] === waterMarkEl) {
// 停止观察。调用该方法后,DOM 再发生变动,也不会触发观察器
observer.disconnect();
// 初始化(设置水印,启动监控)
init(el, binding);
} else if (
type === "attributes" &&
target === waterMarkEl &&
currStyle !== style
) {
waterMarkEl.setAttribute("style", style);
}
}
});
observer.observe(el.parentElement, {
childList: true,
attributes: true,
subtree: true,
});
};
export default {
name: "watermark",
directives,
};
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
const routes: Array = [
{
path: '/',
name: 'HelloWorld',
component: () => import('../views/HelloWorld.vue'),
},
{
path: '/watermark',
name: 'Watermark',
component: () => import('../views/Watermark.vue'),
},
]
const router = createRouter({
history: createWebHashHistory(),
routes,
})
export default router;
出现水印
出现水印在 div 标签上