前端水印组件探索分享

626 阅读3分钟

一、需求背景

1.1 数据安全

出去安全考虑,浏览管理后台的时候必须加上网页水印,来防止用户截图或者录屏暴露敏感信息后无法追踪用户来源。

1.2 前后端技术方案优缺点对比

前端添加水印

安全系数较低,容易被懂前端技术的技术人员消除或者篡改

运算消耗本地浏览器的CPU算力

后端添加水印

安全系数较高,一般用在图片,视频水印上,可单独对源文件处理,难以消除

主要是后端机器生成水印,若碰到复杂情况,对服务器有一定压力

二、实现思路

2.1 技术原理

不影响现有代码是前提,我们可以创建一个和原来HTML BOX一样大小的水印节点,利用CSS属性的position:fixed或者absolute,让水印处于最顶层。

2.2 技术效果

image.png

2.3 技术方案

  • 纯html(创建文字需要太多节点,影响页面渲染,抛弃)
  • html + canvas(生成一张IMG用来当做background,性能相比第一种方案好)
  • html + svg(生成一张IMG用来当做background,性能相比第一种方案好,但兼容性比canvas的差一些,可看下面2个图的对比)

三、代码实现

3.1 画图

    // 创建一个画布
    var can = document.createElement("canvas");
    // 设置画布的长宽
    can.width = options["width"];
    can.height = options["height"];

    var cans = can.getContext("2d");
    // 旋转角度
    cans.rotate((-20 * Math.PI) / 180);
    cans.font = "100 " + options["fontSize"] + " 微软雅黑";
    // 设置填充绘画的颜色、渐变或者模式
    cans.fillStyle = options["color"];
    // 设置文本内容的当前对齐方式
    cans.textAlign = "left";
    // 设置在绘制文本时使用的当前文本基线
    cans.textBaseline = "Middle";
    // 在画布上绘制填色的文本(输出的文本,开始绘制文本的X坐标位置,开始绘制文本的Y坐标位置)
    cans.fillText(content, can.width / 8, can.height / 2);

3.2 生成IMG

canvas的关键API toDataURL

    var div = document.createElement("div");
    // 水印ID命名规范
    div.id = watermarkId;
    div.style.pointerEvents = "none";
    div.style.top = (0) + "px";
    div.style.left = (0) + "px";
    div.style.position = _options.boxId ? "absolute" : "fixed";
    div.style.zIndex = "9999999";
    div.style.width = ( _options.boxId ? _options.boxWidth : document.documentElement.clientWidth) + "px";
    div.style.height = (_options.boxId ? _options.boxHeight : document.documentElement.clientHeight) + "px";
    div.style.background = "url(" + can.toDataURL("image/png") + ") left top repeat";
    // 将水印插入盒子中
    _options.boxId ? document.getElementById(_options.boxId).appendChild(div) : document.body.appendChild(div);

3.3 容错

  • 清除已存在的水印
    var watermarkId = _options.boxId + '-watermark'
    // 如果已经有相同命名的水印,清除掉
    if (document.getElementById(watermarkId) !== null) {
        _options.boxId ?
            document.getElementById(_options.boxId).removeChild(document.getElementById(watermarkId))
            :
            document.body.removeChild(document.getElementById(watermarkId))
            ;
    }
  • 定时监控水印 采用setInterval轮询页面上,水印节点是否消失,如果消失了,重新创建水印
    var watermarkId = setWatermark(content, options, _options);
    setInterval(() => {
        if (document.getElementById(watermarkId) === null) {
            watermarkId = setWatermark(content, options, _options);
        }
    }, 500);
  • 页面大小改变的时候,水印重新渲染
 window.onresize = () => {
        setWatermark(content, options, _options);
    };

四、Vue组件封装和发版

4.1 组件封装

本质上是slot插槽的使用,只有简单的几行,核心代码在上面【代码实现】

<template lang="html">
  <div :id="id" ref="watermask" style="position: relative;width:100%;height:100%">
    <slot></slot>
  </div>
</template>
<script>
import './shuiyin.js' // 核心代码
export default {
  name: "WaterMask",
  props: ['content','options','id'],
  data() {
    return {
      watermarkId: '',
      _options: {}
    }
  },
  mounted() {
    const {width,height} = this.$refs.watermask.getBoundingClientRect()
    this._options = {
      boxWidth: width,
      boxHeight: height,
      boxId: this.id
    }
    window["WaterMask"].set(this.content,this.options,this._options)
  }
};
</script>

4.2 npm发版

发版就简单npm publish发到私库上,使用的时候,可直接在main.js引入组件后直接使用

  • 全局注入组件
import watermask from '@watermask'
Vue.component(watermask.name, watermask)
  • 组件使用
<template> 
    <water-mask :content="content1" :options="options1" :id="id1"> 
        <p>VUE通过refs获取元素宽高</p> 
        <p>VUE通过refs获取元素宽高</p> 
        <p>VUE通过refs获取元素宽高</p> 
        <p>VUE通过refs获取元素宽高</p> 
        <p>VUE通过refs获取元素宽高</p> 
        <p>VUE通过refs获取元素宽高</p> 
        <p>VUE通过refs获取元素宽高</p> 
    </water-mask> 
</template> 
<script> 
export default {
    data () {
        return {
            content1: "水印内容1", options1: {}, id1: 'xxx', 
        } 
    } 
}
</script>

五、参考文档

阿里ProComponents procomponents.ant.design/components/…