Element Plus Watermark 水印组件完全指南:从基础用法到 Canvas 渲染原理
【免费下载链接】element-plus🎉 A Vue.js 3 UI Library made by Element team项目地址: https://gitcode.com/GitHub_Trending/el/element-plus
<el-watermark>是 Element Plus 提供的页面水印组件,用于在页面或指定容器上叠加文本或图片水印,常用于版权保护、身份标识与防截图场景。本文以 docs/en-US/component/watermark.md 为核心骨架,结合仓库源码 packages/components/watermark 的实现细节,系统讲解其全部配置项、四种典型用法以及底层的 Canvas 绘制与防篡改机制,帮助你快速上手并理解其工作原理。
快速上手:基础用法
最基础的用法是给一段内容套上一层水印。组件默认渲染一个position: relative的容器包裹插槽内容,并通过绝对定位的子节点在容器上层叠加平铺的水印:
<template> <el-watermark :font="font"> <div style="height: 500px" /> </el-watermark> </template> <script setup lang="ts"> import { reactive, watch } from 'vue' import { isDark } from '~/composables/dark' const font = reactive({ color: 'rgba(0, 0, 0, .15)', }) watch( isDark, () => { font.color = isDark.value ? 'rgba(255, 255, 255, .15)' : 'rgba(0, 0, 0, .15)' }, { immediate: true } ) </script>示例代码来自 docs/examples/watermark/basic.vue。默认水印文字为Element Plus,颜色为半透明的rgba(0, 0, 0, .15),因此不会遮挡正文内容。上例还演示了在暗色模式下通过响应式font对象自动切换水印颜色的做法——这也是官方文档示例的标准写法,可直接迁移到自己的项目中。
组件会为容器追加一个覆盖层,其样式在 watermark.vue 中生成:绝对定位、宽高占满容器、pointer-events: none保证水印不拦截任何鼠标事件,用户依旧可以正常点击、拖拽被覆盖的内容。
多行文字水印
通过content属性传入字符串数组,即可渲染多行水印文字:
<template> <el-watermark :font="font" :content="['Element+', 'Element Plus']"> <div style="height: 500px" /> </el-watermark> </template>完整示例见 docs/examples/watermark/multi-line.vue。从源码 getMarkSize 可以看到多行水印的尺寸计算逻辑:
- 逐行调用
ctx.measureText测量文本宽度,取所有行中的最大值作为水印宽度; - 水印高度 =
单行高度 × 行数 + (行数 - 1) × fontGap,其中fontGap是行与行之间的间隙(默认 3px); - 测量时优先使用
fontBoundingBoxAscent/Descent,在不支持这两个字段的低版本浏览器(如 Firefox < 116)中回退到actualBoundingBoxAscent/Descent,保证跨浏览器一致性。
图片水印
image属性用于指定水印图片,设置后图片的优先级高于文字内容:
<template> <el-watermark :width="130" :height="30" image="https://element-plus.org/images/element-plus-logo.svg" > <div style="height: 500px" /> </el-watermark> </template>完整示例见 docs/examples/watermark/image.vue。官方文档特别提醒:为保证图片高清且不被拉伸,建议上传至少为期望宽高 2 倍(2x)甚至 3 倍(3x)的图片资源。
源码中图片水印的加载逻辑在 renderWatermark:
- 通过
new Image()异步加载图片,onload后以图片作为水印内容绘制; - 如果图片加载失败(
onerror),会自动回退到文字水印,避免页面出现空白水印; - 同时设置
img.crossOrigin = 'anonymous'与img.referrerPolicy = 'no-referrer',避免跨域图片导致 Canvas 被"污染"而无法导出 DataURL。若你的图片地址涉及跨域,请确保服务端已配置正确的 CORS 响应头。
自定义配置:实时预览水印效果
官方文档提供了"自定义配置"示例(见 docs/examples/watermark/custom.vue),通过表单控件实时驱动水印参数,可以直观预览每种配置的效果:
<template> <el-watermark :content="config.content" :font="config.font" :z-index="config.zIndex" :rotate="config.rotate" :gap="config.gap" :offset="config.offset" > <!-- 被水印覆盖的业务内容 --> </el-watermark> </template> <script setup lang="ts"> import { reactive } from 'vue' const config = reactive({ content: 'Element Plus', font: { fontSize: 16, color: 'rgba(0, 0, 0, 0.15)' }, zIndex: -1, rotate: -22, gap: [100, 100] as [number, number], offset: [] as unknown as [number, number], }) </script>示例中用到了el-input编辑水印文字、el-color-picker调整颜色、el-slider控制字号/层级/旋转角度,以及el-input-number调节间距gap与偏移offset。这种"配置面板 + 实时水印"的组合非常适合在后台管理系统中做成可配置的安全水印。
需要说明的是,示例中zIndex: -1表示把水印层置于容器背景之下,img { z-index: 10 }让图片内容显示在水印之上,二者叠加演示了层级控制的两种取向;实际业务中请根据内容与安全需求决定水印是浮于内容之上还是垫在内容之下。
完整 API:Attributes、Font 与 Slots
Attributes 属性
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
width | 水印的宽度,content存在时默认值为其自身宽度 | number | 120 |
height | 水印的高度,content存在时默认值为其自身高度 | number | 64 |
rotate | 水印绘制时的旋转角度,单位° | number | -22 |
z-index | 追加的水印元素的 z-index | number | 9 |
image | 图片源,建议使用 2x 或 3x 图片,优先级高于文字 | string | — |
content | 水印文字内容 | string \| string[] | Element Plus |
font | 文字样式 | Font | Font |
gap | 水印之间的间距 | [number, number] | [100, 100] |
offset | 水印相对容器左上角的偏移,默认值为gap/2 | [number, number] | [gap[0]/2, gap[1]/2] |
对应 TypeScript 类型定义与默认值可在 watermark.ts 中确认,组件内withDefaults也明确声明了zIndex: 9、rotate: -22、content: 'Element Plus'、gap: [100, 100]四个默认值(见 watermark.vue)。
关于offset的默认行为,源码中有更精细的处理:当未传入offset时,offsetLeft取gap[0] / 2、offsetTop取gap[1] / 2,即默认从gap/2处开始平铺;当显式传入的偏移大于gap/2时,会进一步收缩覆盖层尺寸并调整backgroundPosition,确保水印边缘不会被裁切(见 getMarkStyle)。
Font 字体样式
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
color | 字体颜色 | string | rgba(0,0,0,.15) |
fontSize | 字体大小 | number \| string | 16 |
fontWeight | 字重 | 'normal' \| 'bold' \| 'lighter' \| 'bolder' \| number | normal |
fontFamily | 字体族 | string | sans-serif |
fontGap^(2.11.5) | 字体行间距 | number | 3 |
fontStyle | 字体样式 | 'none' \| 'normal' \| 'italic' \| 'oblique' | normal |
textAlign | 文字对齐方式 | 'left' \| 'right' \| 'center' \| 'start' \| 'end' | center |
textBaseline | 文字基线 | 'top' \| 'hanging' \| 'middle' \| 'alphabetic' \| 'ideographic' \| 'bottom' | hanging |
其中fontGap为 2.11.5 版本新增属性。源码中所有字体属性都有独立的 computed 兜底逻辑,未传入时逐项取默认值(见 watermark.vue)。
textAlign在绘制时会映射为[alignRatio, spaceRatio]组合:left/start为[0, 0.5]、center为[0.5, 0]、right/end为[1, -0.5],分别用于计算文字在画布上的横向起点和旋转预留空间的补偿方向(见 useClips.ts)。
Slots 插槽
| 名称 | 说明 |
|---|---|
default | 水印所覆盖的容器内容 |
组件模板即div[ref=containerRef]+<slot />(见 watermark.vue)。测试用例也验证了插槽内容会被正常渲染(见 watermark.test.tsx)。若不传任何插槽内容,组件会渲染出一个空容器,水印依然会平铺显示,因此它也可以直接包在任意块级元素外层使用。
源码原理:Canvas 绘制、平铺与防篡改
理解这几个源码细节,有助于你在复杂场景下预判组件行为:
1. 高清适配
绘制前先通过getPixelRatio()取window.devicePixelRatio,在 prepareCanvas 中把画布的实际像素尺寸放大ratio倍,再按同样比例缩放字号(mergedFontSize = fontSize * ratio),最后把生成的 DataURL 以 CSS 像素尺寸平铺到背景上。这正是"2x/3x 图片在高分屏上不模糊"以及文字水印始终清晰的底层原因。
2. 旋转与边界裁剪
单块水印先按内容尺寸绘制,再复制到max(width, height)的正方形画布上围绕中心旋转指定角度;随后计算旋转后四角边界(getRotatePos),精确裁出包含完整旋转内容的最小区域,避免斜向水印出现"切头切尾"。最后按gap将三块水印拼成一张更小的平铺贴图(见 useClips.ts),大幅减少背景图的重复绘制开销。
3. 响应式更新与防篡改
组件对props做了deep: true, flush: 'post'的深度监听,任何参数变化都会重新渲染水印。同时通过useMutationObserver监听容器 DOM 变化,一旦检测到水印节点被删除或属性被修改(见 reRendering),会立即销毁并重建水印——这为"用户通过开发者工具删掉水印节点"提供了基础防御能力。组件在onBeforeUnmount时也会主动销毁水印节点,避免内存泄漏。
4. SSR 友好
水印绘制依赖document与canvas,因此useClips被设计为惰性 Hook(源码注释明确说明 "This is a lazy hook function since SSR no need this"),水印只在客户端onMounted后渲染,服务端渲染时不会报错,可安全用于 SSR 项目。
总结与使用建议
<el-watermark>是一个"开箱即用"的轻量组件:默认零配置即可给任意容器铺上倾斜的半透明文字水印;需要更精细的控制时,可通过content(多行)、image(图片,建议 2x/3x 资源)、font(全套字体样式)以及gap/offset/rotate/z-index微调布局与视觉。其内部基于 Canvas 绘制 + DataURL 背景平铺实现,兼顾高分屏清晰度与渲染性能,并通过 MutationObserver 提供基础的防篡改能力。需要说明的是,水印是视觉层面的防护手段,无法阻止高级手段直接修改 DOM 或截图后处理,对强安全场景建议与服务端加解密、权限控制等方案配合使用。
【免费下载链接】element-plus🎉 A Vue.js 3 UI Library made by Element team项目地址: https://gitcode.com/GitHub_Trending/el/element-plus
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考