前言

        为了防止信息泄露或知识产权被侵犯,在web的世界里,对于图片文档等增加水印处理是十分有必要的。水印的添加根据环境可以分为两大类,前端浏览器环境添加和后端服务环境添加。

今天介绍的就是通过canvas创建一张含有水印信息的背景图片,通过vue指令插入到页面中。

配置

在vue项目中创建waterMark.ts文件

对外接口

  • 清除水印(clear)
  • 设置水印(setWatermark)
  • 初始化加载水印(loadingWatermark)

核心功能函数

  • 绘制文字背景图(createBase64)
  • 绘制水印层(createWatermark)
  • 页面随窗口大小调整更新(updateWatermark)
  • 初始化加载水印(loadingWatermark)
export function useWatermark(
  appendEl: Ref<HTMLElement | null> = ref(document.body) as Ref<HTMLElement>
) {
   // 绘制文字背景图
  function createBase64() {}

  // 绘制水印层
  const createWatermark = () => {};

  // 页面随窗口调整更新水印
  function updateWatermark(){}

  // 对外提供的设置水印方法
  function setWatermark() {}

  // 清除水印
  const clear = () => {};

  //初始化加载水印
  const loadingWatermark() {}

  //方便传参预定义一个类型,可自行再扩展
  type attr = {
      font?: string;      //字体
      fontSize?: string;  //字体大小
      fillStyle?: string; //水印颜色
      width?:Number;      //宽度
      height?:Number;     //高度
      fillText?:string;   //水印文本
   };

  return { setWatermark, clear, loadingWatermark };
}

基本代码框架如上,也可自行添加

绘制文字背景图

function createBase64(item?: attr) {
    const can = document.createElement("canvas");
    const width = item?.width ?? 300;
    const height = item?.height ?? 240;
    const font = attr?.fontSize ?? "12px"+" "+attr?.font ?? "Reggae One";
    Object.assign(can, { width, height });

    const cans = can.getContext("2d");
    if (cans) {
      cans.rotate((-20 * Math.PI) / 120);
      cans.font = font;
      cans.fillStyle = attr?.fillStyle ?? "rgba(0, 0, 0, 0.12)";
      cans.textAlign = attr?.textAlign ?? "left";
      cans.textBaseline =attr?.textBaseline ?? "middle";
      cans.fillText(attr?.fillText, width / 20, height);
    }
    return can.toDataURL("image/png");
  }

绘制水印层

这个函数的主要逻辑是先判断如果已经绘制了水印层,直接调用更新水印方法,如果还没有,先动态创建一个 DIV 层,设置绝对定位,铺满当前整个浏览器窗口。

const id = domSymbol.toString();
const watermarkEl = shallowRef<HTMLElement>();

const createWatermark = (item?: attr) => {
    if (unref(watermarkEl)) {
      updateWatermark({ str, attr });
      return id;
    }
    const div = document.createElement("div");
    watermarkEl.value = div;
    div.id = id;
    div.style.pointerEvents = "none";
    div.style.top = "0px";
    div.style.left = "0px";
    div.style.position = "absolute";
    div.style.zIndex = "100000";
    const el = unref(appendEl);
    if (!el) return id;
    const { clientHeight: height, clientWidth: width } = el;
    updateWatermark({ item.fillText, width, height, attr });
    el.appendChild(div);
    return id;
  };

更新水印

因为更新水印方法主要是根据当前窗口高度和宽度来的更新水印背景的设置,利用一张 Base64 格式的图片平铺即可。

function updateWatermark(
    options: {
      width?: number;
      height?: number;
      item?: attr;
    } = {}
  ) {
    const el = unref(watermarkEl);
    if (!el) return;
    if (options.width !== "undefined") {
      el.style.width = `${options.width}px`;
    }
    if (ioptions.height !== "undefined") {
      el.style.height = `${options.height}px`;
    }
    if (options.item?.fillText!== "undefined") {
      el.style.background = `url(${createBase64(
        options.item
      )}) left top repeat`;
    }
  }

到此,我们实现了主要的三个功能函数,下面就是两个对外接口:

设置水印

这里的主要点是考虑设置页面resize监听,来及时更新水印的位置。还要考虑 Vue 的生命周期,当我们卸载页面的时候要进行清除水印。

function setWatermark(attr?: attr) {
    createWatermark(attr);
    addResizeListener(document.documentElement, func);
    const instance = getCurrentInstance();
    if (instance) {
      onBeforeUnmount(() => {
        clear();
      });
    }
  }

  const func = throttle(function () {
    const el = unref(appendEl);
    if (!el) return;
    const { clientHeight: height, clientWidth: width } = el;
    updateWatermark({ height, width });
  });

清除水印

清除水印的时候顺便移除窗口大小监听函数

  const clear = () => {
    const domId = unref(watermarkEl);
    watermarkEl.value = undefined;
    const el = unref(appendEl);
    if (!el) return;
    domId && el.removeChild(domId);
    removeResizeListener(el, func);
  };

//在添加水印后切换页面可能会导致 domId为undefined,这边还提供另外一种清除水印的方法
  const clear1 = () => {
    const domId=document.getElementById(id);
    watermarkEl.value = undefined;
    const el = unref(appendEl);
    if (!el) return;
    domId && el.removeChild(domId);
    removeResizeListener(el, func);
  };

  const clear2 = () => {
     if (document.getElementById(id) !== null) {
           document.body.removeChild(document.getElementById(id));
        }
    };

全局使用或者指定页面

通过在设置水印的参数的时候预留的路由数组来判断,此方法可预留可不预留。下面提供了两种方式

//怎么保存设置看个人,只要方便操作就可以
//watermarkPagesInvolved是我存储路由的数组,dicMenu是调用方法的时候传进来的当前页面路由

function LoadingsetWatermark(item?: attr, dicMenu?: string) {
 if (res.watermarkPagesInvolved.indexOf(dicMenu) != -1) {
          createWatermark(res as any);
        } else {
          clear();
        }
  
  }

水印功能的使用

import { useWatermark } from "/@/hooks/watermark";
import { defineComponent } from 'vue';
export default defineComponent({
  components: {
      useWatermark,
    },

  data() {
        return{
            /**水印设置 */
           formWater: {
               //文本
              filterText: '',
              //颜色
              color: '',
              //字体
              font: 'Vedana',
              //字体大小
              fontSize: 15,
              //垂直对齐
              textBaseline: 'middle',
              //水平对齐
              textAlign: 'left',
              //宽
              width: 300,
              //高
              height: 240,
        }
      }
    },
})

onMounted(() => {
    const { setWatermark, clear } = useWatermark();
  nextTick(() => {
    setWatermark(formWater);
  });
});

onBeforeUnmount(() => {
  clear();
});

指定页面使用

方式1,使用预留的方法

  • 1,在设置水印的时候预留一个数组来存储想要展示页面的路由
  • 2,在App.vue或者mian.ts中监听路由的变化,我这边使用的是在App.vue监听
  import { useWatermark } from './hooks/web/useWatermark';
  import { useRouter } from 'vue-router';
  const { LoadingsetWatermark } = useWatermark();
  const $router = useRouter();

  watch(
    () => $router.currentRoute.value.name,
    (newValue, oldValue) => {
      console.log(!newValue ? oldValue.toString() : newValue.toString());

      LoadingsetWatermark(!newValue ? oldValue.toString() : newValue.toString());
    },
  );

方式二,不使用预留的方法

  • 直接在App.vue或者main.ts判断是否创建水印
    
      watch:{
        $route:{
          handler (val, oldval) {
            let paths = ['/login', '/404', '/403', '/500']
            this.$nextTick(() => {
              if (paths.includes(val.path)) { //如果是以上url 则 不需要水印
                let my = document.getElementById("frame")
                my && my.parentNode.removeChild(my)
              } else if ((!paths.includes(val.path) && paths.includes(oldval.path)) || (!paths.includes(val.path) && oldval.path === '/')) {
                this.$nextTick(() => {
                  this.watermark()
                })
              } else if (!(paths.includes(val.path) && paths.includes(oldval.path)) && !document.getElementById("frame")) {
                this.$nextTick(() => {
                  this.watermark()
                })
              }
            })
          },
          // 深度观察监听
          // deep: true
        }
      },
       watermark() {
    	// 认设置
          let info = getStore({name: 'userInfo'})
          if (isNull(info)) {
            return
          }
            let defaultSettings = {
            watermarl_element: "avue-view", // 要绘制元素的容器id
            watermark_txt: `${info.userName}-<br/>`,
            watermark_img: '',
            watermark_x: 20, // 水印起始位置x轴坐标
            watermark_y: 50, // 水印起始位置Y轴坐标
            watermark_rows: 5, // 水印行数
            watermark_cols: 3, // 水印列数
            watermark_x_space: 100, // 水印x轴间隔
            watermark_y_space: 50, // 水印y轴间隔
            watermark_color: '#3e7ff7', // 水印字体颜色
            watermark_alpha: 0.2, // 水印透明度
            watermark_fontsize: '20px', // 水印字体大小
            watermark_font: '微软雅黑', // 水印字体
            watermark_width: 350, // 水印宽度
            watermark_height: 120, // 水印长度
            watermark_angle: 15 // 水印倾斜度数
            }
            let oTemp = document.createDocumentFragment();
            let maskElement = document.getElementsByClassName(defaultSettings.watermarl_element)[1] || document.body;
            //获取页面最大宽度
            let page_width = Math.min(maskElement.scrollWidth, maskElement.clientWidth);
            //获取页面最大高度
            let page_height = Math.min(maskElement.scrollHeight, maskElement.clientHeight);
            //水印数量自适应元素区域尺寸
            defaultSettings.watermark_cols = Math.ceil(page_width / (defaultSettings.watermark_x_space + defaultSettings.watermark_width));
            defaultSettings.watermark_rows = Math.ceil(page_height / (defaultSettings.watermark_y_space + defaultSettings.watermark_height));
            let x
            let y
            let frame = document.createElement('div')
            frame.setAttribute('id', 'frame')
            oTemp.appendChild(frame)
            for (var i = 0; i < defaultSettings.watermark_rows; i++) {
                y = defaultSettings.watermark_y + (defaultSettings.watermark_y_space + defaultSettings.watermark_height) * i
    	    for (var j = 0; j < defaultSettings.watermark_cols; j++) {
                x = defaultSettings.watermark_x + (defaultSettings.watermark_width + defaultSettings.watermark_x_space) * j
            let mask_div = document.createElement('div')
            mask_div.id = 'mask_div' + i + j
            mask_div.className = 'mask_div';
            // mask_img.className = 'mask_img';				                    
            //mask_div.appendChild(document.createTextNode(defaultSettings.watermark_txt));
            mask_div.innerHTML = (defaultSettings.watermark_txt);
            // mask_img.src = defaultSettings.watermark_img;
            // mask_img.style.width = "150px";
            // mask_img.style.height = "50px";
    
            // 空白图片会有占位,判断src为空时移除img标签  不为空时添加img
            // if(defaultSettings.watermark_img == ""){
            // 	mask_div.remove(mask_img);
            // }else{
            // 	mask_div.append(mask_img);
            // }
            //设置水印div倾斜显示
            mask_div.style.transform = "rotate(-" + defaultSettings.watermark_angle + "deg)"
            mask_div.style.visibility = ""
            mask_div.style.position = "absolute"
            mask_div.style.left = x + 'px'
            mask_div.style.top = y + 'px'
            mask_div.style.overflow = "hidden"
            mask_div.style.zIndex = "9999"
            mask_div.style.pointerEvents = 'none'
            //pointer-events:none  让水印不遮挡页面的点击事件
            //mask_div.style.border="solid #eee 1px"
            //兼容IE9以下的透明度设置
            // mask_div.style.filter="alpha(opacity=50)";
            mask_div.style.opacity = defaultSettings.watermark_alpha
            mask_div.style.fontSize = defaultSettings.watermark_fontsize
            mask_div.style.fontFamily = defaultSettings.watermark_font
            mask_div.style.color = defaultSettings.watermark_color
            mask_div.style.textAlign = "center"
            mask_div.style.width = defaultSettings.watermark_width + 'px'
            mask_div.style.height = defaultSettings.watermark_height + 'px'
            mask_div.style.display = "block"
            frame.appendChild(mask_div)
            }
        }
        maskElement.appendChild(oTemp)
    }
    
    

Logo

前往低代码交流专区

更多推荐