如何优雅地实现一个通用水印组件
代码来自Ant Design,已改写为通用js组件,轻松接入项目😉
在前端开发中,水印组件广泛应用于保护网页内容不被非法复制或未经授权的使用。通过 Canvas 实现的水印组件具有高度的灵活性与性能优势。在本文中,我将详细介绍如何使用 Canvas 构建一个通用的水印组件,并重点讨论如何测量文本宽高来确保水印精确显示。
先看效果
核心思路:通过 Canvas 渲染水印
我们要做的就是在 Canvas 上绘制水印,并将生成的图片用作目标容器的背景。Canvas 的高自由度让我们可以轻松控制水印的样式、位置和透明度等属性。
为什么选择 Canvas 实现水印?
相比其他方法(如图片叠加或 DOM 方式),Canvas 具有以下优势:
- 灵活且高可控:可以精确控制文本的旋转角度、透明度、字体样式等,甚至支持图像作为水印。
- 较难篡改:由于水印直接渲染到 Canvas 中,不容易被简单的 DOM 操作或 CSS 样式删除。
接下来我们将探讨 Canvas 的关键实现部分。
渲染水印的关键:测量文本宽高
在实现水印渲染时,我们首先需要知道文本的宽度和高度,以确保排布的水印不会重叠、超出边界,或影响页面的整体布局。为此,Canvas 提供了 measureText 方法,它可以用来精确测量文本的宽度。
为什么测量文本宽高如此重要?
- 防止文本重叠:在多行文本的水印中,确保每行有足够的间距,以避免水印重叠。
- 动态适应文本长度:水印内容长度不固定时,动态测量宽高,确保所有文本都能显示完整。
- 精准排布:通过精确测量文本尺寸,可以确保水印内容均匀分布,不会显得杂乱无章。
如何使用 measureText 测量文本尺寸?
measureText 方法可以返回文本的宽度,而为了获取文本的高度,我们结合字体的上升(ascent)和下降(descent)信息来计算。
getMarkSize(ctx) {
const { content, font = {}, width, height } = this.options;
let defaultWidth = 120;
let defaultHeight = 64;
// 如果没有图片,则测量文本的宽高
if (!this.options.image && ctx.measureText) {
ctx.font = `${Number(font.fontSize || 16)}px ${font.fontFamily || 'sans-serif'}`;
const contents = Array.isArray(content) ? content : [content];
// 通过 measureText 获取文本的宽度和高度
const sizes = contents.map(item => {
const metrics = ctx.measureText(item);
return [
metrics.width, // 文本的实际宽度
metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent + metrics.ideographicBaseline // 文本的实际高度
];
});
// 确保水印内容不会超出宽高边界
defaultWidth = Math.ceil(Math.max(...sizes.map(size => size[0])));
defaultHeight = Math.floor(Math.max(...sizes.map(size => size[1]))) * contents.length + (contents.length - 1) * getFontGap();
}
return [width ?? defaultWidth, height ?? defaultHeight];
}
代码解读:
ctx.measureText(item).width用于获取文本的宽度。metrics.fontBoundingBoxAscent和metrics.fontBoundingBoxDescent是 Canvas 提供的用于计算文本高度的属性。getFontGap()计算行间距,确保多行文本有足够的间隔。
通过这种方式,我们可以轻松获取每段文本的准确宽度和高度,并确保渲染时不发生重叠或遮挡。
核心实现:Canvas 水印渲染
在测量文本尺寸后,我们使用 Canvas 来渲染水印,并将生成的水印图片应用到目标元素。
javascript
复制代码
function renderWatermark() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 获取设备的像素比率,确保在高分辨率设备上水印效果良好
const ratio = getPixelRatio();
const [markWidth, markHeight] = this.getMarkSize(ctx);
// 根据测量到的文本大小进行绘制
const [nextClips, clipWidth] = renderClips(
this.options.content,
this.options.rotate,
ratio,
markWidth,
markHeight,
this.options.font,
this.options.gap[0],
this.options.gap[1],
this.options.globalAlpha,
this.options.isDebug
);
// 将渲染出的水印应用到目标容器中
this.appendWatermark(nextClips, clipWidth, this.options.container);
}
渲染过程的详细步骤:
- 创建 Canvas:为水印创建一个新的 Canvas 元素,并获取其 2D 渲染上下文。
- 计算像素比率:为了兼容高清屏幕设备,使用
getPixelRatio函数获取设备的像素比率,以调整画布的分辨率。 - 获取文本大小:通过之前定义的
getMarkSize方法获取水印文本的宽度和高度。 - 绘制水印:调用
renderClips函数,根据测量到的文本宽高及其他设置(如旋转角度、透明度等)进行绘制。 - 应用水印:将生成的水印图像作为背景图应用到目标元素中。
renderClips 函数的实现
renderClips 函数负责实际的绘制过程。它将文本内容或者图片按照设定的样式绘制到 Canvas 上,然后通过重复背景实现整个水印的排布。
javascript
复制代码
function renderClips(content, rotate, ratio, width, height, font, gapX, gapY, globalAlpha, isDebug = false) {
const [ctx, canvas, contentWidth, contentHeight] = prepareCanvas(width, height, ratio, isDebug);
if (content instanceof HTMLImageElement) {
ctx.drawImage(content, 0, 0, contentWidth, contentHeight);
} else {
const { color, fontSize, fontStyle, fontWeight, fontFamily, textAlign } = font;
const mergedFontSize = Number(fontSize) * ratio;
ctx.font = `${fontStyle} normal ${fontWeight} ${mergedFontSize}px/${height}px ${fontFamily}`;
ctx.fillStyle = color;
ctx.textAlign = textAlign;
ctx.textBaseline = 'top';
const contents = Array.isArray(content) ? content : [content];
contents.forEach((item, index) => {
ctx.fillText(item ?? '', contentWidth / 2, index * (mergedFontSize + getFontGap() * ratio) + patchWordRenderHeight(item));
});
}
// 绘制水印的旋转、透明度及间隔逻辑
// ...省略部分代码
}
总结
通过本文,我们详细展示了如何通过 Canvas 实现一个高效的水印组件,并重点介绍了 文本宽高测量 在水印排版中的重要性。使用 measureText 和其他 Canvas API,我们可以精确控制文本的宽度和高度,从而确保水印的美观与功能性。
使用 Canvas 实现水印是一种高效、灵活且安全的方式,特别适用于需要动态内容和复杂排版的场景。通过这种方式,前端开发者可以轻松地构建出强大且优雅的水印组件。完整代码点此进入,在线预览,npm