前端实现水印效果

一、问题背景

为了防止信息泄露或知识产权被侵犯,在web的世界里,对于页面和图片等增加水印处理是十分有必要的,水印的添加根据环境可以分为两大类,前端浏览器环境添加和后端服务环境添加,简单对比一下这两种方式的特点:

前端浏览器加水印:

  • 减轻服务端的压力,快速反应
  • 安全系数较低,对于掌握一定前端知识的人来说可以通过各种骚操作跳过水印获取到源文件
  • 适用场景:资源不跟某一个单独的用户绑定,而是一份资源,多个用户查看,需要在每一个用户查看的时候添加用户特有的水印,多用于某些机密文档或者展示机密信息的页面,水印的目的在于文档外流的时候可以追究到责任人 后端服务器加水印:
  • 当遇到大文件密集水印,或是复杂水印,占用服务器内存、运算量,请求时间过长
  • 安全性高,无法获取到加水印前的源文件
  • 适用场景:资源为某个用户独有,一份原始资源只需要做一次处理,将其存储之后就无需再次处理,水印的目的在于标示资源的归属人 这里我们讨论前端浏览器环境添加

二、实现效果

 

三、实现方案

1. 重复的dom元素覆盖实现

第一时间想到的方案是在页面上覆盖一个position:fixed的div盒子,盒子透明度设置较低,设置pointer-events: none;样式实现点击穿透,在这个盒子内通过js循环生成小的水印div,每个水印div内展示一个要显示的水印内容,简单实现

这种方案需要要在js内循环创建多个dom元素,既不优雅也影响性能,于是考虑可不可以不生成这么多个元素。

2. canvas画出背景图 MutationObserver监听

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>水印</title>
</head>

<body>
    <div id="content">
        <p>这是一段内容</p>
    </div>

    <script>

        // 定义水印函数
        function addWatermark({
            container = document.body, // 水印添加到的容器,默认为 body
            width = "300px", // 水印 canvas 的宽度
            height = "250px", // 水印 canvas 的高度
            textAlign = "center", // 水印文字的对齐方式
            textBaseline = "middle", // 水印文字的基线
            font = "16px Microsoft Yahei", // 水印文字的字体
            fillStyle = "rgba(184, 184, 184, 0.6)", // 水印文字的填充样式
            content = "水印", // 水印文字的内容
            rotate = "45", // 水印文字的旋转角度
            zIndex = 10000, // 水印的 z-index 值
        }) {
            // 生成水印 canvas
            const canvas = document.createElement("canvas");
            canvas.setAttribute("width", width);
            canvas.setAttribute("height", height);
            const ctx = canvas.getContext("2d");
            ctx.textAlign = textAlign;
            ctx.textBaseline = textBaseline;
            ctx.font = font;
            ctx.fillStyle = fillStyle;
            ctx.rotate((Math.PI / 180) * rotate);
            ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2);

            // 将 canvas 转换为 base64 URL
            const base64Url = canvas.toDataURL();
            const __wm = document.querySelector('.__wm');
            const watermarkDiv = __wm || document.createElement("div");
            const styleStr = `
                        position: fixed;
                        top: -70px;
                        left: 0;
                        bottom: 0;
                        right: 0;
                        width: 100%;
                        height: 100%;
                        z-index: ${zIndex};
                        pointer-events: none;
                        background-repeat: repeat;
                        background-image: url('${base64Url}')
                    `;
            watermarkDiv.setAttribute("style", styleStr);
            watermarkDiv.classList.add("__wm"); 
           //则创建一个 div 并设置样式和类名

            if (!__wm) {
                container.style.position = 'relative';
                container.insertBefore(watermarkDiv, container.firstChild);
            }
            
        }



        // 调用 addWatermark 函数添加水印
        addWatermark({
            container: document.getElementById("content"),
            width: "300px",
            height: "200px",
            textAlign: "center",
            textBaseline: "middle",
            font: "16px Microsoft Yahei",
            fillStyle: "rgba(184, 184, 184, 0.3 )",
            content: "水印 6512",
            rotate: "30",
            zIndex: 10000,
        });
    </script>
</body>

</html>

方法存在一个共同的问题,由于是前端生成dom元素覆盖到页面上的,对于有些前端知识的人来说,可以在开发者工具中找到水印所在的元素,将元素整个删掉,以达到删除页面上的水印的目的,针对这个问题,我想到了一个很笨的办法:设置定时器,每隔几秒检验一次我们的水印元素还在不在,有没有被修改,如果发生了变化则再执行一次覆盖水印的方法。网上看到了另一种解决方法:使用MutationObserver

MutationObserver是变动观察器,字面上就可以理解这是用来观察节点变化的。Mutation Observer API 用来监视 DOM 变动,DOM 的任何变动,比如子节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。

但是MutationObserver只能监测到诸如属性改变、子结点变化等,对于自己本身被删除,是没有办法监听的,这里可以通过监测父结点来达到要求。监测代码的实现:

 // 监听容器变化,当容器发生变化时重新调用 addWatermark 函数
              const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
                if (MutationObserver) {
                    let mo = new MutationObserver(function () {
                        const __wm = document.querySelector('.__wm');
                        // 只在__wm元素变动才重新调用__canvasWM
                        if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) {
                            // 避免一直触发
                            mo.disconnect();
                            mo = null;
                            console.log('1');
                            addWatermark({
                                container: document.getElementById("content"),
                                width: "300px",
                                height: "200px",
                                textAlign: "center",
                                textBaseline: "middle",
                                font: "16px Microsoft Yahei",
                                fillStyle: "rgba(184, 184, 184, 0.3 )",
                                content: "孙淼 6512",
                                rotate: "30",
                                zIndex: 10000,
                            });
                        }
                    });

                    mo.observe(container, {
                        attributes: true,
                        subtree: true,
                        childList: true
                    });
                }

最终代码:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>水印</title>
</head>

<body>
    <div id="content">
        <p>这是一段内容</p>
    </div>

    <script>

        // 定义水印函数
        function addWatermark({
            container = document.body, // 水印添加到的容器,默认为 body
            width = "300px", // 水印 canvas 的宽度
            height = "250px", // 水印 canvas 的高度
            textAlign = "center", // 水印文字的对齐方式
            textBaseline = "middle", // 水印文字的基线
            font = "16px Microsoft Yahei", // 水印文字的字体
            fillStyle = "rgba(184, 184, 184, 0.6)", // 水印文字的填充样式
            content = "水印", // 水印文字的内容
            rotate = "45", // 水印文字的旋转角度
            zIndex = 10000, // 水印的 z-index 值
        }) {
            // 生成水印 canvas
            const canvas = document.createElement("canvas");
            canvas.setAttribute("width", width);
            canvas.setAttribute("height", height);
            const ctx = canvas.getContext("2d");
            ctx.textAlign = textAlign;
            ctx.textBaseline = textBaseline;
            ctx.font = font;
            ctx.fillStyle = fillStyle;
            ctx.rotate((Math.PI / 180) * rotate);
            ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2);

            // 将 canvas 转换为 base64 URL
            const base64Url = canvas.toDataURL();
            const __wm = document.querySelector('.__wm');
            const watermarkDiv = __wm || document.createElement("div");
            const styleStr = `
                        position: fixed;
                        top: -70px;
                        left: 0;
                        bottom: 0;
                        right: 0;
                        width: 100%;
                        height: 100%;
                        z-index: ${zIndex};
                        pointer-events: none;
                        background-repeat: repeat;
                        background-image: url('${base64Url}')
                    `;
            watermarkDiv.setAttribute("style", styleStr);
            watermarkDiv.classList.add("__wm"); 
           //则创建一个 div 并设置样式和类名

            if (!__wm) {
                container.style.position = 'relative';
                container.insertBefore(watermarkDiv, container.firstChild);
            }
              // 监听容器变化,当容器发生变化时重新调用 addWatermark 函数
              const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
                if (MutationObserver) {
                    let mo = new MutationObserver(function () {
                        const __wm = document.querySelector('.__wm');
                        // 只在__wm元素变动才重新调用__canvasWM
                        if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) {
                            // 避免一直触发
                            mo.disconnect();
                            mo = null;
                            console.log('1');
                            addWatermark({
                                container: document.getElementById("content"),
                                width: "300px",
                                height: "200px",
                                textAlign: "center",
                                textBaseline: "middle",
                                font: "16px Microsoft Yahei",
                                fillStyle: "rgba(184, 184, 184, 0.3 )",
                                content: "孙淼 6512",
                                rotate: "30",
                                zIndex: 10000,
                            });
                        }
                    });

                    mo.observe(container, {
                        attributes: true,
                        subtree: true,
                        childList: true
                    });
                }
        }



        // 调用 addWatermark 函数添加水印
        addWatermark({
            container: document.getElementById("content"),
            width: "300px",
            height: "200px",
            textAlign: "center",
            textBaseline: "middle",
            font: "16px Microsoft Yahei",
            fillStyle: "rgba(184, 184, 184, 0.3 )",
            content: "水印 6512",
            rotate: "30",
            zIndex: 10000,
        });
    </script>
</body>

</html>

前端背景加水印是一种常见的需求,主要用于保护图片、文档或其他内容不被盗用或滥用。通过将文本、图案等信息作为“水印”添加到图像上,可以标明版权归属或提供其他相关信息。 ### 实现步骤 1. **准备画布 (Canvas)** - 利用 HTML5 的 `<canvas>` 元素生成一个透明的画布。 2. **加载目标图片** - 使用 JavaScript 加载需要处理的目标图片(例如用户上传的一张照片),并将其绘制到 Canvas 上。 3. **绘制水印** - 设置字体样式、颜色以及旋转角度等内容属性,在适当位置开始书写文字型水印;也可以插入矢量图形来充当复杂形状的标志型水印。 4. **合并显示结果** - 将带有叠加效果的新图像导出为 Base64 编码字符串或是直接下载保存下来供后续使用。 ```javascript function addWatermark(imageUrl, text){ var canvas = document.createElement('canvas'); var ctx = canvas.getContext("2d"); // 调整大小匹配原图尺寸 const img = new Image(); img.src = imageUrl; img.onload=function(){ canvas.width=img.width; canvas.height=img.height; // 绘制原始图像至画布上 ctx.drawImage(img ,0, 0); // 添加自定义风格配置给上下文环境变量对象ctx ctx.font="italic bold 80px Arial"; ctx.fillStyle="#FFFFFF"; ctx.globalAlpha=0.5; // 计算居中坐标值用于放置标注文字 let x=(img.width)/2; let y=(img.height)/2; // 应用变换矩阵前先记录当前状态以便恢复 ctx.save(); ctx.translate(x,y); ctx.rotate(-Math.PI / 4); //-45度角倾斜 // 输出实际数据串入画面内 ctx.fillText(text,-x+50,-y+50); // 返回操作后复位之前设定好的各项参数限制条件 ctx.restore(); }; } ``` #### 注意事项 - 确保所有资源文件都已正确引入,并妥善管理跨域请求权限设置。 - 对于动态生成的内容要考虑性能优化问题避免阻塞主线程工作流程过久影响用户体验感。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值