[Ruimo.com] Front-end realizes page watermark based on DOM or Canvas

foreword

We will see many pages with watermarks, but how to achieve it? Of course, there are many ways to achieve it. This article mainly explains how to realize the watermark effect based on DOM or Cavans in the vue project. Of course, there are other ways to achieve it, such as adding a watermark to the original picture to generate a new picture, but this requires a backend. deal with. Because it is to be used in the vue project, I can directly implement the watermark effect on the mounted dom by using custom instructions.

The project environment for watermarking in this article is: vue + vite + ts

1. Explanation of vue custom instruction directive

There is a special article explaining the detailed explanation of custom instructions in vue2.x and vue3.x

2. DOM-based implementation

1. Organize ideas

  • Get the width and height
    (1) Get the actual width of the bound element clientWidth
    (2) Get the actual height of the bound element clientHeight
    (3) Get the parent element parentElement of the bound element

  • Create a box
    (1) Create a box that wraps the watermark image
    (2) Create a box that wraps the watermark image

  • Set the box style
    (1) The width and height of the wrapped watermark box are the width and height of the bound element, namely clientWidth, clientHeight
    (2) Set the background image, rotation degree, width and height, and click through for the watermark box

  • Set the position of the created element
    (1) The watermark box is placed in the box that wraps the watermark image (the box that wraps the watermark image wraps the watermark) (
    2) The box that wraps the watermark image is placed before the bound element
    (3) The bound element Put it in the box that wraps the watermark image (otherwise the bound element is at the same level as the box that wraps the watermark image)

2. New index.vue

Put the watermark instruction on the label, and set the width and height of the label. The watermark can be enlarged on the div tag or on the img tag. Note: img only has the onload method, but does the div tag have it.

<script setup lang="ts">
import { ref } from "vue";
</script>
<template>
    <div class="index-content" >
        <div class="watermaker" v-watermark ></div>
         <!-- <img v-watermark style="width:400px;height:400px" src="../assets/vue.svg" alt=""> -->
    </div>
</template>

<style scoped>
.watermaker {
    width: 400px;
    height: 400px;
}
.index-content{
    width: 100%;
    height: 100%;
}
</style>

3. Create a new directives file

Create a waterMark.ts file under the directives file , the specific content is as follows:

import waterImg from "@/assets/vue.svg"
const directives: any = {
    mounted(el: HTMLElement) {
        //如果el元素是img,则可以用el.onload将下面包裹
        const { clientWidth, clientHeight, parentElement } = el;
        console.log(parentElement, 'parentElement')

        const waterMark: HTMLElement = document.createElement('div');
        const waterBg: HTMLElement = document.createElement('div');
      
        //设置waterMark的class和style
        waterMark.className = `water-mark`;
        waterMark.setAttribute('style', `
            display: inline-block;
            overflow: hidden;
            position: relative;
            width: ${clientWidth}px; 
            height: ${clientHeight}px;`);

        // 创建waterBg的class和style
        waterBg.className = `water-mark-bg`;// 方便自定义展示结果
        waterBg.setAttribute('style', `
            position: absolute;
            pointer-events: none;`在这里插入代码片`
            transform: rotate(45deg);
            width: 100%;
            height: 100%;
            opacity: 0.2;
            background-image: url(${waterImg}); 
            background-repeat: repeat;
            `);
            
        // 水印元素waterBg放到waterMark元素中
        waterMark.appendChild(waterBg);
        //waterMark插入到el之前,即插入到绑定元素之前
        parentElement?.insertBefore(waterMark, el);
        // 绑定元素移入到包裹水印的盒子
        waterMark.appendChild(el);
    }
}
export default {
    name: 'watermark',
    directives
}

4. Create an index.ts file under the directives file

import type { App } from 'vue'
import watermark from './waterMark'

export default function installDirective(app: App) {
    app.directive(watermark.name, watermark.directives);
} 

5. Introduce globally in main.ts

import { createApp } from 'vue'
import App from './App.vue'
import directives from './directives'
const app = createApp(App);
app.use(directives);
app.mount('#app');

6. Disadvantages

(1). When deleting the watermark element directly, the watermark in the page will be deleted directly. Of course, we can use MutationObserver to monitor the watermark element. When deleting, we can immediately generate a watermark element. The specific aspects are explained below .

(2). If the original element itself has rules such as css positioning, it will affect the overall layout effect, because the above implementation excludes that the original element has no positioning, so the implementation method is not very rigorous. This article implements it in detail

  • Create a watermark container set to position: relati

  • Put the original node into this container

  • At the same time, create a dom with a watermark and set it to position: absolute, so that the watermark element can be overlaid on the upper layer of the original element to achieve the effect of the watermark.

3. Implementation based on Canvas and MutationObserver

1. Organize ideas

  • Configure the specific style of the watermark (size, rotation angle, text fill)

  • set watermark (position)

  • Monitor dom changes (prevent the page from displaying the watermark after the watermark is deleted)

2. Generate watermark

By drawing the picture in cavans , and then converting the picture to base64 encoding through the toDataURL method of cavans .

// 全局保存 canvas 和 div ,避免重复创建(单例模式)
const globalCanvas = null;
const globalWaterMark = null;

// 获取 toDataURL 的结果
const getDataUrl = (
  // font = "16px normal",
  // fillStyle = "rgba(180, 180, 180, 0.3)",
  // textAlign,
  // textBaseline,
  // text = "请勿外传",
) => {
  const rotate = -10;
  const canvas = globalCanvas || document.createElement("canvas");
  const ctx = canvas.getContext("2d"); // 获取画布上下文

  ctx.rotate((rotate * Math.PI) / 180);
  ctx.font = "16px normal";
  ctx.fillStyle = "rgba(180, 180, 180, 0.3)";
  ctx.textAlign = "left";
  ctx.textBaseline = "middle";
  ctx.fillText('请勿外传', canvas.width / 3, canvas.height / 2);
  return canvas.toDataURL("image/png");
};

3. Use MutationObserver to monitor the watermark

Use MutationObserver to monitor dom changes. The detailed usage of MutationObserver has been mentioned before. The details can be seen that you still don’t understand MutationObserver as a front-end? that's Out

The specific monitoring logic is as follows:

1. Delete dom directly

(1) First get the dom where the watermark is set

(2) Monitor the dom of the deleted element

(3) If the two are equal, stop observing and initialize (set watermark + start monitoring)

2. Delete the attributes in the style

(1) Determine whether the deletion is an attribute of the tag (type === "attributes")

(2) Determine whether the deleted label attribute is on the label with the watermark set

(3) Judging the comparison between the modified style and the previous style, if not equal, reassign

// watermark 样式
let style = `
display: block;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-repeat: repeat;
pointer-events: none;`;

//设置水印
const setWaterMark = (el: HTMLElement, binding: any) => {
  const { parentElement } = el;
  // 获取对应的 canvas 画布相关的 base64 url
  const url = getDataUrl(binding);

  // 创建 waterMark 父元素
  const waterMark = globalWaterMark || document.createElement("div");
  waterMark.className = `water-mark`; // 方便自定义展示结果
  style = `${style}background-image: url(${url});`;
  waterMark.setAttribute("style", style);

  // 将对应图片的父容器作为定位元素
  parentElement.setAttribute("style", "position: relative;");
  // 将图片元素移动到 waterMark 中
  parentElement.appendChild(waterMark);
};

// 监听 DOM 变化
const createObserver = (el: HTMLElement, binding: any) => {
  console.log(el, 'el')
  console.log(style, 'style')
  // console.log(el.parentElement.querySelector('.water-mark'),'el.parentElement')
  const waterMarkEl = el.parentElement.querySelector(".water-mark");
  const observer = new MutationObserver((mutationsList) => {
    console.log(mutationsList, 'mutationsList')
    if (mutationsList.length) {
      const { removedNodes, type, target } = mutationsList[0];
      const currStyle = waterMarkEl.getAttribute("style");
      // console.log(currStyle, 'currStyle')
      // 证明被删除了
    //   (1)直接删除dom
    //   1.先获取设置水印的dom
    //   2.监听到被删除元素的dom
    //   如果他两相等的话就停止观察,初始化(设置水印+启动监控)
    //   (2) 删除style中的属性
    //  1 判断删除的是否是标签的属性 (type === "attributes")
    //  2.判断删除的标签属性是否是在设置水印的标签上
    //  3.判断修改过的style和之前的style对比,不等的话,重新赋值
      if (removedNodes[0] === waterMarkEl) {
        console.log(removedNodes[0])
        // 停止观察。调用该方法后,DOM 再发生变动,也不会触发观察器。
        observer.disconnect();
        //初始化(设置水印,启动监控)
        init(el, binding);
      } else if (
        type === "attributes" &&
        target === waterMarkEl &&
        currStyle !== style
      ) {
        console.log(currStyle, 'currStyle')
        console.log(style, 'style')
        waterMarkEl.setAttribute("style", style);
      }
    }
  });
  observer.observe(el.parentElement, {
    childList: true,
    attributes: true,
    subtree: true,
  });
};
// 初始化
const init = (el: HTMLElement, binding: any = {}) => {
  // 设置水印
  setWaterMark(el, binding.value);
  // 启动监控
  createObserver(el, binding.value);
};
const directives: any = {
  mounted(el: HTMLElement, binding: any) {
  //注意img有onload的方法,如果自定义指令注册在html标签的话,只需要init(el, binding.value)
    el.onload = init.bind(null, el, binding.value);
  },
};

4. Results display

The delete watermark label is still there, unless the watermark registration label is deleted to delete the watermark, but it is meaningless to do so, because doing so will also delete all the content.

Attachment: Basic knowledge of js used in the article

toDataURL usage

toDataURL(type, encoderOptions) , receiving two parameters:

  • type: image type, such as image/png, image/jpeg, image/webp, etc., the default is image/png format

  • encoderOptions: The value range of image quality (0-1), the default value is 0.92, when the limit is exceeded, the default value is 0.92

Guess you like

Origin blog.csdn.net/rrmod/article/details/129022139