在 web 的世界里,对于图片文档等增加水印处理是十分有必要的。水印的添加根据环境可以分为两大类,前端浏览器环境添加和后端服务环境添加。 通过 canvas 创建一张含有水印信息的背景图片,通过 hooks 函数插入到页面中。
对外暴露方法
- 设置水印 setWatermark
- 清除水印 clear
核心功能
- 创建水印 createWatermark
- 更新水印 updateWatermark
- 根据文字创建 canvas 背景图 createBase64
export function useWatermark(
appendEl: Ref<HTMLElement | null> = ref(document.body) as Ref<HTMLElement>,
) {
// 清除水印
const clear = () => {};
// 创建 canvas 背景图
function createBase64(str: string) {}
// 更新水印
function updateWatermark() {}
// 创建水印
const createWatermark = (str: string) => {};
// 设置水印
function setWatermark(str: string) {}
return { setWatermark, clear };
}
创建 canvas 背景图
function createBase64 (str: string) {
const can = document.createElement('canvas')
const width = 300
const height = 240
Object.assign(can, { width, height })
const cans = can.getContext('2d')
if (cans) {
cans.rotate((-20 * Math.PI) / 120);
cans.font = '15px Vedana';
cans.fillStyle = 'rgba(0, 0, 0, 0.15)';
cans.textAlign = 'left';
cans.textBaseline = 'middle';
cans.fillText(str, width / 20, height);
}
return can.toDataURL('image/png')
}
创建水印
动态创建一个 div 标签,设置绝对定位,整个浏览器窗口铺满,若已存在水印层,则直接调用 updateWatermark 方法更新水印。
const id = domSymbol.toString()
const watermarkEl = shallowRef<HTMLElement>()
const createWatermark = (str: string) => {
if (unref(watermarkEl)) {
updateWatermark({ str })
return id
}
const div = document.createElement('div')
watermarkEl.value = div
div.id = id
div.style.pointerEvents = 'none'
div.style.top = '0px';
div.style.left = '0px';
div.style.position = 'absolute';
div.style.zIndex = '100000';
const el = unref(appendEl)
if (!el) return id
const { clientHeight: height, clientWidth: width } = el
updateWatermark({ str, width, height })
el.appendChild(div)
return id
}
更新水印
使用 createBase64 方法创建 Base64 格式的图片来铺满整个窗口。
function updateWatermark(
options: {
width?: number;
height?: number;
str?: string;
} = {},
) {
const el = unref(watermarkEl);
if (!el) return;
if (isDef(options.width)) {
el.style.width = `${options.width}px`;
}
if (isDef(options.height)) {
el.style.height = `${options.height}px`;
}
if (isDef(options.str)) {
el.style.background = `url(${createBase64(options.str)}) left top repeat`;
}
}
设置水印
在设置水印时,不仅需要创建水印,还需要设置 resize 监听来更新水印的位置,以及 Vue 生命周期中,卸载页面时清除水印等操作。
function setWatermark(str: string) {
createWatermark(str);
addResizeListener(document.documentElement, func);
const instance = getCurrentInstance();
if (instance) {
onBeforeUnmount(() => {
clear();
});
}
}
const func = useRafThrottle(function () {
const el = unref(appendEl);
if (!el) return;
const { clientHeight: height, clientWidth: width } = el;
updateWatermark({ height, width });
});
清除水印
清除水印时,需要移出窗口的监听函数。
const clear = () => {
const domId = unref(watermarkEl);
watermarkEl.value = undefined;
const el = unref(appendEl);
if (!el) return;
domId && el.removeChild(domId);
removeResizeListener(el, func);
};
组件使用
import { defineComponent, ref } from 'vue'
import { useWatermark } from '/@/hooks/web/useWatermark'
export default defineComponent({
setup () {
const { setWatermark, clear } = useWatermark();
return {
setWatermark,
clear,
}
}
})
利用 ResizeObserver Polyfill 库实现DOM调整大小监听和移除监听
window.resize 事件能帮我们监听窗口大小的变化。但是 resize 事件会在一秒内触发将近60次,所以很容易在改变窗口大小时导致性能问题。换句话说,window.resize 事件通常是浪费的,因为它会监听每个元素的大小变化(只有window对象才有resize事件),而不是具体到某个元素的变化。
ResizeObserver API 使用了观察者模式,即常说的发布-订阅模式, 来监听一个DOM节点的变化,这种变化包括但不仅限于:某个节点的出现和隐藏、某个节点的大小变化
- 安装依赖
npm install resize-observer-polyfill --save-dev
- 封装方法
// src/utils/event/index.ts
import ResizeObserver from 'resize-observer-polyfill';
const isServer = typeof window === 'undefined';
/* istanbul ignore next */
function resizeHandler(entries: any[]) {
for (const entry of entries) {
// 通过 entry.target 访问监听的 DOM 对象 上的 __resizeListeners__ 属性, 遍历存储的监听回调
const listeners = entry.target.__resizeListeners__ || [];
if (listeners.length) {
listeners.forEach((fn: () => any) => {
fn();
});
}
}
}
/* istanbul ignore next */
// 接受DOM元素和方法
export function addResizeListener(element: any, fn: () => any) {
if (isServer) return;
if (!element.__resizeListeners__) {
// 在 DOM 元素对象上,设置 __resizeListeners__ 属性,存储监听器(回调函数)。
element.__resizeListeners__ = [];
// 监听DOM元素的变化
element.__ro__ = new ResizeObserver(resizeHandler);
// 监听指定的 Element 或 SVGElement
element.__ro__.observe(element);
}
element.__resizeListeners__.push(fn);
}
/* istanbul ignore next */
export function removeResizeListener(element: any, fn: () => any) {
if (!element || !element.__resizeListeners__) return;
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
if (!element.__resizeListeners__.length) {
// 取消 element.__ro__ 观察者身上所有的元素的观察
element.__ro__.disconnect();
}
}