前言

在 Web 开发中,水印是一种常见的安全措施,用于保护内容的版权和防止非法复制。本文将介绍一种基于 JavaScript 的 Web 前端水印方案,包括水印的生成和动态更新机制。

前端水印方案有那些

css 背景图水印

最简单的水印方式

 .watermark-box {
        width: 100%;
        height: 100vh;
        background-image: url('watermark.png');
    }

SVG 水印

SVG:可缩放矢量图形,是一种基于可扩展标记语言(XML),用于描述二维矢量图形的图形格式

创建一个 watermark.svg

<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
  <text x="0" y="30" fill="#000000" font-size="20" font-family="Arial" opacity="0.3" transform="rotate(-10)">
    测试水印
  </text>
</svg>

body {
        background-image: url('./watermark.svg');
        background-repeat: repeat;
        background-position: center;
        background-size: 100px 50px;
    }

0. 宽高限制水印的大小 这里为100*100的水印

  1. 显示文本需要用到text标签
  2. x 和 y 属性定义了文本左上角的坐标,即文本的起始点位置
  3. font-size 属性定义了文本的字体大小,以像素为单位。
  4. fill 属性定义了文本的颜色。
  5. transform 控制 x,y 轴的偏移和旋转角度 一般水印都会有一些倾斜。

批量复制 dom 实现水印

创建一个遮罩容器 根据可视容器的宽度和高度计算需要多少个小水印 可以动态生成水印内容

缺点重复生成 dom,不是很优雅

<!DOCTYPE html>
<html lang="en">
​
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
​
<style>
    #watermark-box {
        width: 100%;
        height: 100vh;
        position: absolute;
        z-index: -1;
        top: 0;
        left: 0;
        display: flex;
        flex-wrap: wrap;
        padding: 0;
        margin: 0;
    }
​
    .watermark-item {
        transform: rotate(-30deg);
        opacity: 0.2;
    }
</style>
​
<body>
    <div id="watermark-box"></div>
    <div>我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文</div>
</body>
​
<script>
    function initWaterMark(text,el) {
        const waterHeight = 100
        const waterWidth = 100
        const { clientWidth, clientHeight } =
            document.documentElement || document.body
        const column = Math.ceil(clientWidth / waterWidth)
        const rows = Math.ceil(clientHeight / waterHeight)
        for (let i = 0; i < column * rows; i++) {
            const wrap = document.createElement('div')
            wrap.setAttribute('class', 'watermark-item')
            wrap.style.width = waterWidth + 'px'
            wrap.style.height = waterHeight + 'px'
            wrap.textContent = text
            el.appendChild(wrap)
        }
    }
    window.onload = () => {
        const el = document.getElementById("watermark-box")
        initWaterMark('测试水印',el)
    }
</script>
​
</html>

canvas+伪类

<!DOCTYPE html>
<html lang="en">
​
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
​
</style>
​
<body>
    <div class="watermark-box">
        <div>我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文</div>
    </div>
</body>
​
<script>
​
    function createBackgroundImage(options) {
        const { content, H, W } = options;
        const canvas = document.createElement('canvas'); // 创建画布
        const ctx = canvas.getContext('2d');
        canvas.width = H;
        canvas.height = W;
        if (ctx) {
            ctx.rotate(-0.3); // 旋转文字
            ctx.fillStyle = 'rgba(0,0,0,0.2)'; // 设置字体颜色
            ctx.font = '40px'; // 设置字体的大小
            ctx.fillText(content, 30, 30);// 填充水印内容
        }
        return canvas.toDataURL('image/png'); // 转换成dataURL,可以直接设置成背景图片
    };
​
    function getWaterMark({ content, className, H = 120, W = 120, }) {
        const dataURL = createBackgroundImage({ content, H, W });
        // 设置伪类样式
        const defaultStyle = document.createElement('style');
        defaultStyle.innerHTML = `.${className}::after {
                content: '';
                display: block;
                width: 100%;
                height: 100vh;
                background-image: url(${dataURL});
                background-repeat: repeat;
                pointer-events: none;
                position: fixed;
                top: 0;
                left: 0;
            }`;
        document.head.appendChild(defaultStyle);
    };
​
​
    window.onload = () => {
        getWaterMark({
            content: "测试水印",
            className: 'watermark-box',
        });
    }
​
​
</script>
​
</html>

SVG+伪类

同 canvas+伪类 相同 只需要替换 canvas 绘制的水印变成 svg 绘制的即可

​
    function getWaterMark({ content, className, H = 120, W = 120, }) {
        //....
​
        const svgStr = `<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
               <text x="0" y="30" fill="#000000" font-size="20" font-family="Arial" opacity="0.3" transform="rotate(-10)">
                 ${content}
               </text>
              </svg>`
        const dataURL = 'data:image/svg+xml;base64,'window.btoa(unescape(encodeURIComponent(svgStr)));
      
      //....
    };

水印安全

Shadow DOM

随着 Web 程序规模的增长,很容易遇到样式和逻辑冲突的问题。为了解决这些问题,Web 标准引入了 Shadow DOM,可以帮助我们更好地隔离和封装组件。Shadow DOM 是 Web Components 标准的一部分,是 Web Components 中的一个关键技术。它提供了一种将 HTML 结构、样式和行为封装在一个独立的、封闭的 DOM 中的机制。这意味着,使用 Shadow DOM 可以将组件的样式和结构隐藏在组件作用域内,防止其与全局样式或逻辑发生冲突。

所以可以把水印元素放在 Shadow DOM 中,从而在一定程度上防止篡改。

基本使用方法

使用 Element.attachShadow() 方法来将一个 shadow root 附加到任何一个元素上。

它接受一个配置对象作为参数,该对象有一个 mode 属性,值可以是 open 或者 closed 。

open 表示可以通过页面内的 JavaScript 方法来获取 Shadow DOM

closed 则表示不可以从外部获取 Shadow DOM 。

例子:

 const el = document.getElementById("watermark-box")
 const shadowRoot = el.attachShadow({ mode: 'closed' })

MutationObserver

MutaitionObserver 的作用主要是用于监听当前页面的 dom 变化情况,当所监听的 dom 发生变化时,可以通过回调函数做出响应,适用于我们希望得知当前页面的 dom 元素在何时发生了什么样的变化。

水印作为背景图 设置为style 样式 监听背景图是否被修改 如果被修改则 重新生成水印

  function initMutationObserver(container) { 
        var mutation = new MutationObserver((mutationsList) => {
            for (let mutation of mutationsList) {
                if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
                    getWaterMark({
                        content: "测试水印",
                        className: 'watermark-box',
                    });
                }
            }
        });
​
        // 安装监听
        mutation.observe(container, {
            childList: true,
            attributes: true,
            characterData: true,
            subtree: true,
            attributeOldValue: true,
            characterDataOldValue: true,
            attributeFilter: ['class', 'style']
​
        });
​
    }
    const container = document.querySelector(".watermark-box");
    initMutationObserver(container);

总结

以上几种前端水印方案可以根据自己的需要进行选择,web 水印可以保护页面内容的版权和完整性,防止被篡改和盗用。但是不存在绝对安全的前端水印方案。前端水印只是防范的一种手段,对于重要和敏感的内容,还需要结合其他保护措施来综合保护。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐