前端水印剖析

发布于 2023-07-25 11:14:08 字数 5772 浏览 33 评论 0

目的:信息安全,信息泄露可追踪

特点:包含一段标识信息,同时需要覆盖足够的区域

需求:

  • 作为背景图
  • 文字样式可调整

那么怎么生成自定义背景图呢???

Canvas

HTMLCanvasElement.toDataURL 该方法返回一个包含图片展示的 data URI

    const canvas = document.createElement('canvas');
    canvas.setAttribute('width', width);
    canvas.setAttribute('height', height);
    var 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);
    
    var base64Url = canvas.toDataURL();

SVG

SVG:可缩放矢量图形是一种基于可扩展标记语言,用于描述二维矢量图形的图形格式。使用 SVG 生成图片的方式和 Canvas 的方式类似,只是 base64Url 的生成方式换成了 SVG

const svgStr = 
`<svg xmlns="namespace" width="${width}" height="${width}">
      <text x="50%" y="50%" dy="12px"
        text-anchor="middle"
        stroke="#000000"
        stroke-width="1"
        stroke-opacity="${opacity}"
        fill="none"
        transform="rotate(-45, 120 120)"
       >
        ${content}
      </text>
</svg>`;
const base64Url = `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgStr)))}`;

了解如何生成背景图之后,生成背景图后跟水印又有什么关系呢?开门见山的说吧,水印是可以通过在 Dom 节点上添加 background-image 属性来实现的。那么怎么给目标节点添加 background-image 属性呢?来看看 Vue React 项目中如何实现的吧。下文提到的imageURI为上文提到的 base64Url 哦

Vue

1、自定义指令,在需要打上水印的节点上添加 v-watermarked 指令

Vue.directive('watermarked', {
  bind(el, binding, vnode) {
    if (binding.value === undefined || !!binding.value) {
      el.style.backgroundImage = imageURI ;
      el.style.backgroundRepeat = 'space repeat';
    }
  },
  update(el, binding, vnode) {
    if (binding.value === undefined || !!binding.value) {
      el.style.backgroundImage = imageURI;
      el.style.backgroundRepeat = 'space repeat';
    }
  },
});

2、封装组件,在需要打上水印的节点外层包上

<template>
  <div
    class="cc-watermark"
    :style="{
      backgroundRepeat: 'space repeat',
      backgroundImage:imageURI,
  }">
    <slot />
  </div>
</template>

react

1、封装组件

<watermark>...</watermark>

2、直接写进组件样式里面

import styled from 'styled-components';
const Root = styled.div`...`

3、高阶组件

const drawPattern = (waterInfo) => {
 canvas 操作同上...
 return new Promise((resolve, reject) => {
    if(window.signature) {
      resolve(window.signature);
    }
    if (process.env.NODE_ENV === 'development') {
    // 开发环境下 canvas 图像信息保存在内存中
      canvas.toBlob((blob) => {
        window.signature = URL.createObjectURL(blob);
        resolve(URL.createObjectURL(blob));
      });
    } else {
      window.signature = canvas.toDataURL();
      resolve(canvas.toDataURL());
    }
  });
}

export default function (WrappedComponent) {
  return class extends Component {
    constructor() {
      super();
      this.state = {
        imageURI: '',
      }
    }
    componentDidMount() {
      drawPattern('chensen 8535').then((blob) => {
        this.setState({
          imageURI: blob,
        });
      });
    }
    render() {
      const ContainerStyle = {
        backgroundImage: `url('${this.state.imageURI}')`,
      };
      return (
      <Root style={ContainerStyle}>
        <WrappedComponent/>
      </Root>
      );
    }
  };
}

现在你已经大概了解如何给页面打上水印了,你发现有什么问题了吗?其实这存在一个弊端,用户通过开发者工具动态更改 DOM 属性或者结构,就可以轻松删除掉水印。那么我们该如何阻止该行为呢?继续吧

MutationObserver

MutationObserver 给开发者们提供了能在某个范围内的 DOM 树发生变化时作出适当反应的能力。

使用 MutationObserver 构造函数,新建一个观察器实例,实例的有一个回调函数,该回调函数接受两个参数,第一个是变动数组(包含一系列变动记录 MutationRecord ),第二个是观察器实例。MutationObserver 的实例的 observe 方法用来启动监听,它接受两个参数。第一个参数:所要观察的 DOM 节点,第二个参数:一个配置对象,指定所要观察的特定变动( config )。

MutationObserver 只能监测到诸如属性改变,增删子节点等,但需要注意的是对于自己本身被删除,是没有办法的,可以用过监测父节点来达到要求。

拿阔爱的立方写个栗子吧,可以在监听到用户删除 style 属性操作,及时恢复水印。

    const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
    const config = {
      attributes: true, //观察受监视元素的属性值更改
      attributeOldValue: true, // 记录任何有改动的属性的上一个值
    };
    const callback = (mutationList, observer) => {
    /*mutationList 包含一系列变动记录*/
      _.forEach(mutationList, (mutationRecord) => {
        const { type, attributeName } = mutationRecord;
        /*
        type 更改类型
        attributeName 返回被修改属性的属性名
        */
        if (type === 'attributes' && attributeName === 'style') {
          observer.disconnect(); //先停止监听 否则会不断触发
          const { target, oldValue } = mutationRecord;
          /*
          target 为受监视元素
          oldValue 被更改属性值的旧值
          */
          target.setAttribute('style', oldValue);
          observer.observe(target, config); // 修改完后恢复监听
        }
      });
    };
    Vue.directive('watermarked', {
      bind(el, binding, vnode) {
        if (binding.value === undefined || !!binding.value) {
          el.style.backgroundImage = imageURI;
          el.style.backgroundRepeat = 'space repeat';
          const om = new MutationObserver(callback);
          om.observe(el, config);
        }
      },
    });

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

赠意

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

qq_E2Iff7

文章 0 评论 0

Archangel

文章 0 评论 0

freedog

文章 0 评论 0

Hunk

文章 0 评论 0

18819270189

文章 0 评论 0

wenkai

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文