前端基于DOM或者Canvas实现页面水印

2023-2-13    前端达人

前言

我们会看到很多页面带有水印,但是怎么实现呢?当然可以有多种实现方式,本文主要讲解在vue项目中基于DOM或者Cavans实现水印效果,当然还有其他的实现方式,比如在原图片的基础上加上水印生成新的图片,但是这需要后端处理。因为要在vue项目中使用,所以我使用自定义指令可以直接对挂载的dom实现水印效果。

本文实现水印的项目环境为:vue + vite + ts

一、vue自定义指令directive讲解

前面专门有一篇讲解vue2.x与vue3.x中自定义指令详解

二、基于DOM的实现方式

1. 思路整理

  • 获取宽高
    (1)获取绑定元素的实际宽度clientWidth
    (2)获取绑定元素实际高度clientHeight
    (3)获取绑定元素的父元素parentElement
  • 创建盒子
    (1)创建一个包裹水印图片的盒子
    (2)创建一个水印图片的盒子
  • 设置盒子样式
    (1)包裹水印盒子宽高为绑定元素的宽高,即clientWidth、clientHeight
    (2)水印盒子设置背景图、旋转度、宽高、点击穿透
  • 设置创建的元素的位置
    (1)水印盒子放到包裹水印图片的盒子里 (包裹水印图片的盒子包裹水印)
    (2)包裹水印图片的盒子放到被绑定元素之前
    (3)被绑定元素放到裹水印图片的盒子里(不然被绑定元素与包裹水印图片的盒子层级同级)

2.新建index.vue

将水印的指令放到标签上,设置标签的宽高。水印可以放大div标签上,也可以是img标签上。注意:img才有onload方法,div标签么有。

<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> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

3. 新建directives文件

directives文件下创建waterMark.ts 文件,具体内容实现如下:

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 } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

4. 在directives文件下创建 index.ts文件

import type { App } from 'vue' import watermark from './waterMark' export default function installDirective(app: App) { app.directive(watermark.name, watermark.directives); } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

5. 在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'); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

6. 缺点

  • 直接删除水印元素时,页面中的水印直接就被删除了,当然我们可以用MutationObserver对水印元素进行监听,删除时,我们再立即生成一个水印元素就可以了,具体方面在下面讲解。
  • 如果原始元素本身存在 css 定位等规则,会导致整体布局效果出现影响,因为上面实现排除了原始元素没有定位,所以实现方式不是很严谨,本文具体实现实现如下:
    • 创建一个水印的容器设置为 position:relative
    • 将原有的节点放入到这个容器中
    • 同时创建一个带有水印的 dom 设置为position:absolute ,实现这个水印元素覆盖到原始元素的上层,以实现水印的效果。

三、基于Canvas和MutationObserver的实现方式

1. 思路整理

  • 配置水印的具体样式(大小,旋转角度,文字填充)
  • 设置水印(位置)
  • 监听dom变化(防止水印删除后页面不再展示水印)

2. 生成水印

通过将图片绘制在cavans中,然后通过cavanstoDataURL方法,将图片转为base64编码。

// 全局保存 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"); }; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3. 使用MutationObserver监听水印

使用MutationObserver监听dom变化,MutationObserver详细用法之前已经讲过了,详细可见作为前端你还不懂MutationObserver?那Out了
具体监听逻辑如下:

  • 1.直接删除dom
    (1)先获取设置水印的dom
    (2)监听到被删除元素的dom
    (3)如果他两相等的话就停止观察,初始化(设置水印+启动监控)
  • 2.删除style中的属性
    (1)判断删除的是否是标签的属性 (type === “attributes”)
    (2)判断删除的标签属性是否是在设置水印的标签上
    (3)判断修改过的style和之前的style对比,不等的话,重新赋值
// 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); }, }; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87

四、成果展示

删除水印标签依然还在,除非删除水印注册的标签才能删除水印,但是这样做毫无意义,因为这样做内容也会全部删除掉。

在这里插入图片描述

附:文中用到的js基础知识

toDataURL用法

toDataURL(type, encoderOptions),接收两个参数:

  • type:图片类型,比如image/png、image/jpeg、image/webp等等,默认为image/png格式
  • encoderOptions:图片质量的取值范围(0-1),默认值为0.92,当超出界限按默认值0.92






蓝蓝设计建立了UI设计分享群,每天会分享国内外的一些优秀设计,如果有兴趣的话,可以进入一起成长学习,请加蓝小助,微信号:ben_lanlan,报下信息,蓝小助会请您入群。欢迎您加入噢~~希望得到建议咨询、商务合作,也请与我们联系01063334945。


分享此文一切功德,皆悉回向给文章原作者及众读者.
免责声明:蓝蓝设计尊重原作者,文章的版权归原作者。如涉及版权问题,请及时与我们取得联系,我们立即更正或删除。


蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务UI设计公司、界面设计公司、UI设计服务公司、数据可视化设计公司、UI交互设计公司、高端网站设计公司、UI咨询、用户体验公司、软件界面设计公司

分享本文至:

日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档