关于前端如何实现水印
前端水印的实现
网页水印
实现思路
- 通过canvas生成一张水印图片
- 通过css将图片设置为目标节点的背景图
- 通过MutationObserver监听目标节点的类名变化,防止水印被删除
代码操作
- 通过canvas生成一张水印图片
function createImgBase(options) {
const { content, width, height } = options;
const canvasDom = document.createElement("canvas");
let ctx = canvasDom.getContext("2d");
canvasDom.width = width;
canvasDom.height = height;
if (ctx) {
// 设置画笔的方向
ctx.rotate((-14 * Math.PI) / 180);
// 设置水印样式
ctx.fillStyle = "rgba(100,100,100,0.4)";
ctx.font = "italic 20px Arial";
// 渲染水印
content.forEach((text, index) => {
ctx.fillText(text, 10, 30 * (index + 1)); // 纵向拉开30的间距
});
}
// document.body.appendChild(canvasDom);
// 将canvas转为图片
return canvasDom.toDataURL("image/png");
}
// createImgBase({
// content: ["介四嘛呀", "介四sui印", "内部机密材料", "严禁外泄!"],
// width: 200,
// height: 200,
// });
- 将水印设置为目标节点的背景图片
function getWaterMark({
content,
className,
canvasHeight = 140,
canvasWidth = 150,
}) {
// 生成图片
const data_url = createImgBase({
content,
width: canvasWidth,
height: canvasHeight,
});
// 通过设置伪元素样式,添加水印图片为背景图
const defaultStyle = `
.${className} {
position: relative;
}
.${className}::after {
content: "";
background-image: url(${data_url});
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
pointer-events: none;
}`;
const styleDom = document.createElement("style");
styleDom.innerHTML = defaultStyle;
document.head.appendChild(styleDom);
}
// getWaterMark({
// content: ["介四嘛呀", "介四sui印", "内部机密材料", "严禁外泄!"],
// className: "content",
// });
- 添加mutationObserver监听节点的变化
function listenerDOMChange(className) {
// 获取要监听的节点
const targetNode = document.querySelector(`.${className}`);
// 创建监听器
const observer = new MutationObserver(mutationList => {
// 遍历变化记录
for (let mutationRecord of mutationList) {
// 如果目标节点的class属性发生变化,判断是不是类名被删了,是的话把类名加回去
if (mutationRecord.attributeName === "class") {
if(!Array.from(targetNode.classList).includes(className)) {
targetNode.classList.add(className)
}
}
}
});
// 启动监听
observer.observe(targetNode, {
attributes: true,
});
}
function getWaterMark({
content,
className,
canvasHeight = 140,
canvasWidth = 150,
}) {
// 监听
listenerDOMChange(className);
const data_url = createImgBase({
content,
width: canvasWidth,
height: canvasHeight,
});
// ...
const styleDom = document.createElement("style");
styleDom.innerHTML = defaultStyle;
document.head.appendChild(styleDom);
}
关于MutationObserver
MutationObserver 用来监听DOM的变化,DOM的增删、DOM属性的变化,子结点和文本内容的变化,都可以被监听。
MutationObserver 的监听和事件不同,事件是同步的,DOM的变化会立即触发对应的事件,而 MutationObserver 是异步的,会在下一个微任务执行时触发监听回调。
- 创建MutationObserver
const observer = new MutationObserver((mutationsList, observer) => {
// mutationsList mutationRecord数组 记录了DOM的变化
// observer MutationObserver的实例
// 监听回调
console.log(mutationsList, observer);
})
- 开启监听
// node 监听的节点
// config 监听配置(要监听哪些内容)
// observer.observe(node, config);
mutationObserver.observe(document.documentElement, {
attributes: true, // 属性变化
attributeOldValue: true, // 观察attributes变动时,是否需要记录变动前的属性值
attributeFilter: [‘class’,‘src’] // 需要观察的特定属性
characterData: true, // 节点内容、文本的变化
characterDataOldValue: true, // 观察characterData变动时,是否需要记录变动前的属性值
childList: true, // 子结点变化
subtree: true, // 所有后代节点
});
// 停止监听
mutationObserver.disconnect()
// 清除变动记录
mutationObserver.takeRecords()
图片水印
实现思路
方案一:通过oss添加水印 方案二:通过canvas生成带有水印的图片
oss实现
oss方式不做过多描述了 简单来说就是通过在获取图片时,在图片链接上增加参数,让oss生成一张带水印的图片。 注意点:
-
png图片的透明区域无法被添加水印。 解决方式: 可通过添加参数的方式,让oss将图片转为jpg格式(jpg格式会对透明区域做颜色填充)。
-
字体大小写为定值,原图大小会影响到水印字体的显示大小。 解决方式:通过创建img标签,onLoad获取图片后,根据图片宽高计算合适的字体大小,然后再一次获取带水印的图片。
-
用户通过删除参数的方式可以删除水印 解决方式:设置oss的安全级别,不带水印不可访问。
canvas实现
- 将img转为canvas
async function imgToCanvas(cav, imgSrc) {
const img = new Image();
img.src = imgSrc;
// 防止因跨域导致的图片加载失败(该方法有局限性)
img.setAttribute("crossOrigin", "anonymous");
// 等待图片加载
await new Promise(resolve => (img.onload = resolve));
cav.width = img.width;
cav.height = img.height;
const ctx = cav.getContext("2d");
if (ctx) {
ctx.drawImage(img, 0, 0);
}
return cav;
}
- 添加水印
function addWaterMask(cav, content) {
const ctx = cav.getContext("2d");
ctx.fillStyle = "rgba(100,100,100,0.2)";
ctx.font = `24px serif`;
ctx.translate(0, 0);
ctx.rotate((5 * Math.PI) / 180);
// 生成水印
let x = 0, y = 0;
while (x < cav.width) {
y = 0;
while (y < cav.height) {
ctx.fillText(content, x, y);
y += 100;
}
x += 150;
}
}
- 使用
(async function () {
const canvas = document.createElement("canvas");
await imgToCanvas(
canvas,
"图片地址"
);
addWaterMask(canvas, "介四sui印");
// document.body.appendChild(canvas);
return canvas.toDataUrl("image/png")
})();
转载自:https://juejin.cn/post/7377645747448741923