在开发vue项目中,可能会根据项目的需求对页面添加水印效果,下面将介绍一种基于vue指令而实现水印的方法(通用于vue2和vue3),利用MutationObserver 监控水印DOM发生变化时,重新渲染水印,防止用户从DOM中直接删除水印。
一、vue指令 ywl-watermark-vue
https://www.npmjs.com/package/ywl-watermark-vue
二、安装使用
npm i ywl-watermark-vue
vue2使用
import Vue from 'vue' import Watermark from 'ywl-watermark-vue' Vue.use(Watermark)
或者
import Watermark from 'ywl-watermark-vue' Vue.directive(Watermark.name, Watermark)
vue3使用
import {createApp, inject} from "vue"; const app = createApp(App); import Watermark from 'ywl-watermark-vue' app.use(Watermark)
组件上使用
<div class="home" v-watermark> <div class="text-center"> <img src="@/assets/images/1.webp" alt=""> </div> </div>
<div class="home" v-watermark="{text:'默认文字',color:'red',size:16}"> hello world </div>
三、效果图
四、实现代码
const globalCanvas = document.createElement('canvas'); const globalWaterMark = document.createElement('div'); let waterMarkObserver = null let waterMarkStyle = '' // 定义指令配置项 export default { // ----- vue2 ---- // 初始化设置 bind (el, binding, vnode) { binding.def?.init(el, binding) }, // 元素插入父元素时调用 inserted (el, binding, vnode) { }, // 组件更新时调用 update (el, binding, vnode) { }, // 组件及子组件更新后调用 componentUpdated (el, binding, vnode) { }, // 解绑时调用 unbind (el, binding, vnode) { // 停止观察。调用该方法后,DOM 再发生变动,也不会触发观察器 waterMarkObserver && waterMarkObserver.disconnect(); }, // ----- vue2 ---- // ----- vue3 ---- // 在绑定元素的 attribute 或事件监听器被应用之前调用 created (el, binding, vnode, prevNode) { binding.dir?.init(el, binding) }, // 当指令第一次绑定到元素并且在挂载父组件之前调用。 beforeMount () { }, // 在绑定元素的父组件被挂载后调用,大部分自定义指令都写在这里。 mounted () { }, // 在更新包含组件的 VNode 之前调用。 beforeUpdate () { }, // 在包含组件的 VNode 及其子组件的 VNode 更新后调用。 updated () { }, // 在卸载绑定元素的父组件之前调用 beforeUnmount () { }, // 当指令与元素解除绑定且父组件已卸载时,只调用一次。 unmounted () { // 停止观察。调用该方法后,DOM 再发生变动,也不会触发观察器 waterMarkObserver && waterMarkObserver.disconnect(); }, // ----- vue3 ---- /** * 初始化水印 * @param el * @param binding */ init (el, binding) { // 设置水印 binding.def?.setWaterMark(el, binding) || binding.dir?.setWaterMark(el, binding); // 启动监控 binding.def?.createObserver(el, binding) || binding.dir?.createObserver(el, binding); }, /** * 默认配置 * @returns {{fontFamily: string, color: string, size: number, text: string}} */ defaultOptions () { return { // 水印文字 text: '默认水印文字', // 水印文字颜色 color: 'rgba(150, 150, 150, 1)', // 水印字体大小 size: 12, // 水印字体类型 fontFamily: 'Arial', } }, /** * 水印容器class名 * @returns {string} */ waterMarkName () { return 'lx-water-mark' }, /** * 水印容器样式 * @returns {string} */ waterMarkStyle () { let style = { 'display': 'block', 'overflow': 'hidden', 'position': 'absolute', 'left': '0px', 'top': '0px', 'z-index': 100000, 'font-size': '12px', 'background-repeat': 'repeat', 'background-position': 'center', 'pointer-events': 'none', 'width': '100%', 'height': '100%' } let styleArr = Object.keys(style).map((key) => { return `${key}:${style[key]}` }) return styleArr.join(';') + ';' }, /** * 设置水印 * @param el * @param binding */ setWaterMark (el, binding) { const parentEl = el; const {width, height} = parentEl?.getBoundingClientRect(); // 拼接配置 let defaultOptions = binding.def?.defaultOptions() || binding.dir?.defaultOptions() if (Object.prototype.toString.call(binding.value) === '[object Object]') { defaultOptions = Object.assign(defaultOptions, { text: binding.value.text || defaultOptions.text, color: binding.value.color || defaultOptions.color, size: binding.value?.size?.toString().replace('px', '') || defaultOptions.size, fontFamily: binding.value.fontFamily || defaultOptions.fontFamily, }) } // 获取对应的 canvas 画布相关的 base64 url const url = binding.def?.getDataUrl(defaultOptions) || binding.dir?.getDataUrl(defaultOptions); // 创建 waterMark 父元素 const waterMark = globalWaterMark || document.createElement('div'); waterMark.className = binding.def?.waterMarkName() || binding.dir?.waterMarkName(); // 方便自定义展示结果 waterMarkStyle = `${binding.def?.waterMarkStyle() || binding.dir?.waterMarkStyle()};background-image: url(${url})`; waterMark.setAttribute('style', waterMarkStyle); // 如果父元素有自己的stayle 则获取后和自定义的拼接,并避免重复添加 let currStyle = parentEl?.getAttribute('style') ? parentEl?.getAttribute('style') : ''; currStyle = currStyle?.includes('position: relative') ? currStyle : currStyle + 'position: relative;'; // 将对应图片的父容器作为定位元素 parentEl?.setAttribute('style', currStyle); // 将图片元素移动到 waterMark 中 parentEl?.appendChild(waterMark); }, /** * 生成水印图片,返回一个包含图片展示的数据 URL * @param options * @returns {string} 水印图片:base64-url */ getDataUrl (options) { const {text, size, fontFamily, color} = options; const rotate = -20; const canvas = globalCanvas || document.createElement('canvas'); const ctx = canvas.getContext('2d'); // 获取canvas画布的绘图环境 canvas.width = 300; // 单个水印大小,宽度 canvas.height = 150; // 高度 ctx.fillStyle = 'rgba(0, 0, 0, 0)'; // 背景填充色 ctx.fillRect(0, 0, 300, 150); // 填充区域大小 ctx.save(); ctx.font = `${size}px ${fontFamily}`; // 文字字体大小 ctx.fillStyle = color; // 文字颜色 ctx.translate((300) / 2, (150) / 2); // 平移,旋转的中心点 ctx?.rotate((rotate * Math.PI) / 360); // 水印旋转角度 ctx.textBaseline = 'middle'; // 垂直居中 ctx.textAlign = 'center'; // 水平居中 ctx?.fillText(text, 0, 0); ctx.restore(); ctx.clip(); return canvas.toDataURL('image/png'); }, /** * 添加观察者,监听DOM变化,用 MutationObserver 对水印元素进行监听,删除、属性变化时,再立即生成一个水印元素 * @param el * @param binding */ createObserver (el, binding) { const className = binding.def?.waterMarkName() || binding.dir?.waterMarkName(); const waterMarkEl = el.querySelector(`.${className}`); waterMarkObserver = new MutationObserver((mutationsList) => { if (mutationsList.length) { const {removedNodes, type, target} = mutationsList[0]; const currStyle = waterMarkEl?.getAttribute('style'); // 证明被删除了 if (removedNodes[0] === waterMarkEl) { // 停止观察。调用该方法后,DOM 再发生变动,也不会触发观察器 waterMarkObserver.disconnect(); // 重新初始化(设置水印,启动监控) binding.def?.init(el, binding) || binding.dir?.init(el, binding); } else if (type === 'attributes' && target === waterMarkEl && currStyle !== waterMarkStyle) { waterMarkEl.setAttribute('style', waterMarkStyle); } } }); waterMarkObserver.observe(el, {attributes: true, childList: true, subtree: true, attributeOldValue: true}); } };
在watermark的index.js文件中引入水印js(./main/index.js)
import Watermark from './main/index.js' Watermark.install = function (Vue) { Vue.directive('watermark', Watermark) } Watermark.name = 'watermark' export default Watermark