UNPKG

vue3-watermark-plugin

Version:

A powerful Vue 3 watermark plugin with component and directive support

181 lines (148 loc) 4.49 kB
/** * 水印指令插件 * 使用方式: * v-watermark="{ content: '水印文本', fontSize: 16, color: '#000', bold: false, rotate: -20, gapX: 100, gapY: 100 }" */ // 默认配置 const defaultConfig = { content: '水印文本', fontSize: 16, color: 'rgba(0, 0, 0, 0.15)', bold: false, rotate: -20, gapX: 100, gapY: 100, zIndex: 9999, }; // 创建水印元素 function createWatermark(config) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // 设置字体 const fontWeight = config.bold ? 'bold' : 'normal'; ctx.font = `${fontWeight} ${config.fontSize}px Arial, sans-serif`; // 测量文本尺寸 const textMetrics = ctx.measureText(config.content); const textWidth = textMetrics.width; const textHeight = config.fontSize; // 计算旋转后的画布尺寸 const radians = (config.rotate * Math.PI) / 180; const cos = Math.abs(Math.cos(radians)); const sin = Math.abs(Math.sin(radians)); const canvasWidth = Math.ceil(textWidth * cos + textHeight * sin) + config.gapX; const canvasHeight = Math.ceil(textWidth * sin + textHeight * cos) + config.gapY; canvas.width = canvasWidth; canvas.height = canvasHeight; // 重新设置字体(canvas重置后需要重新设置) ctx.font = `${fontWeight} ${config.fontSize}px Arial, sans-serif`; ctx.fillStyle = config.color; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; // 移动到画布中心并旋转 ctx.translate(canvasWidth / 2, canvasHeight / 2); ctx.rotate(radians); // 绘制文本 ctx.fillText(config.content, 0, 0); return canvas.toDataURL(); } // 创建水印容器 function createWatermarkContainer(el, config) { const watermarkId = 'watermark-' + Math.random().toString(36).substr(2, 9); // 移除已存在的水印 removeWatermark(el); const watermarkDataUrl = createWatermark(config); const watermarkDiv = document.createElement('div'); watermarkDiv.id = watermarkId; watermarkDiv.style.cssText = ` position: absolute !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; pointer-events: none !important; z-index: ${config.zIndex} !important; background-image: url(${watermarkDataUrl}) !important; background-repeat: repeat !important; background-position: 0 0 !important; `; // 确保父元素有相对定位 const position = window.getComputedStyle(el).position; if (position === 'static') { el.style.position = 'relative'; } el.appendChild(watermarkDiv); // 存储水印ID到元素上 el._watermarkId = watermarkId; return watermarkDiv; } // 移除水印 function removeWatermark(el) { if (el._watermarkId) { const existingWatermark = el.querySelector(`#${el._watermarkId}`); if (existingWatermark) { existingWatermark.remove(); } delete el._watermarkId; } } // 更新水印 function updateWatermark(el, config) { const mergedConfig = { ...defaultConfig, ...config }; if (!mergedConfig.content) { return; } createWatermarkContainer(el, mergedConfig); } // 防篡改监听 function observeWatermark(el) { if (el._watermarkObserver) { el._watermarkObserver.disconnect(); } const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList') { const watermarkExists = el.querySelector(`#${el._watermarkId}`); if (!watermarkExists && el._watermarkConfig) { // 水印被删除,重新创建 updateWatermark(el, el._watermarkConfig); } } }); }); observer.observe(el, { childList: true, subtree: true, }); el._watermarkObserver = observer; } // 指令定义 const watermarkDirective = { mounted(el, binding) { const config = binding.value || {}; el._watermarkConfig = config; updateWatermark(el, config); observeWatermark(el); }, updated(el, binding) { const config = binding.value || {}; el._watermarkConfig = config; updateWatermark(el, config); }, beforeUnmount(el) { removeWatermark(el); if (el._watermarkObserver) { el._watermarkObserver.disconnect(); delete el._watermarkObserver; } delete el._watermarkConfig; }, }; // 导出指令和工具函数 export default watermarkDirective; export { createWatermark, createWatermarkContainer, removeWatermark, updateWatermark, defaultConfig, };