vue3-watermark-plugin
Version:
A powerful Vue 3 watermark plugin with component and directive support
181 lines (148 loc) • 4.49 kB
JavaScript
/**
* 水印指令插件
* 使用方式:
* 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,
};