Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

前端水印 #2

Open
Lindysen opened this issue Feb 26, 2019 · 0 comments
Open

前端水印 #2

Lindysen opened this issue Feb 26, 2019 · 0 comments

Comments

@Lindysen
Copy link
Owner

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

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

需求:

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

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

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)"
        style="font-size: ${fontSize};">
        ${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';
    }
  },
});

  1. 封装组件,在需要打上水印的节点外层包上
<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);
        }
      },
    });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant