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

news2024/12/24 9:42:09

🐱 个人主页:不叫猫先生
🙋‍♂️ 作者简介:前端领域新星创作者、阿里云专家博主,专注于前端各领域技术,共同学习共同进步,一起加油呀!
💫系列专栏:vue3从入门到精通、TypeScript从入门到实践
📢 资料领取:前端进阶资料以及文中源码可以找我免费领取
🔥 前端学习交流:博主建立了一个前端交流群,汇集了各路大神,一起交流学习,期待你的加入!(文末有我wx或者私信)

目录

  • 前言
  • 一、vue自定义指令directive讲解
  • 二、基于DOM的实现方式
    • 1. 思路整理
    • 2.新建index.vue
    • 3. 新建`directives`文件
    • 4. 在`directives`文件下创建 `index.ts`文件
    • 5. 在`main.ts`中全局引入
    • 6. 缺点
  • 三、基于Canvas和MutationObserver的实现方式
    • 1. 思路整理
    • 2. 生成水印
    • 3. 使用MutationObserver监听水印
  • 四、成果展示
  • 附:文中用到的js基础知识
    • toDataURL用法

前言

我们会看到很多页面带有水印,但是怎么实现呢?当然可以有多种实现方式,本文主要讲解在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>

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
}

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);
} 

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');

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");
};

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);
  },
};

四、成果展示

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

在这里插入图片描述

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

toDataURL用法

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/185669.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AtCoder Beginner Contest 287(A~E)

比赛名称&#xff1a;UNIQUE VISION Programming Contest 2023 New Year (AtCoder Beginner Contest 287) 比赛链接&#xff1a;AtCoder Beginner Contest 287 目录 A - Majority B - Postal Card C - Path Graph? D - Match or Not E - Karuta A - Majority 问字…

工作和学习中都能用到的5款实用软件

如今&#xff0c;工作和学习都离不开电脑&#xff0c;所以电脑里的软件自然也是必不可少的&#xff0c;但是电脑软件那么多&#xff0c;不可能每个都装上吧&#xff0c;所以我们要装好用的、实用的&#xff0c;下面给大家分享5款好用到爆的软件&#xff0c;很多懂电脑的人都在用…

【算法】雪花算法

一.特点 1.全局唯一性&#xff1a;对于大数据量的分库分表场景&#xff0c;例如水平分表需要保证主键id的全局唯一性。 2.趋势递增&#xff1a;整体的id趋势是递增的&#xff0c;不是单调递增。 3.不规则性&#xff1a;id不连续&#xff0c;无规则&#xff0c;不规则。 4.包含…

乾元通多卡聚合通信设备保障生态环境监测网络

针对目前城市大气环境监测网格化建设&#xff0c;推出的新一代城市网格化大气环境监测系统&#xff0c;可以实现城市区域环境多维一体化监测管理&#xff0c;该设备主要用于监测大气环境中的PM10、TSP、PM2.5等颗粒物浓度&#xff0c;还可以实现环境监控&#xff0c;测噪音、大…

Node.JS 安装配置 | 安装排错解析

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Node.js下载 Node.js官方下载地址 官方下载如果慢&#xff0c;请用如下地址下载&#xff1a; Node.js 中文网 根据自己计算机配置下载 Next Next 安装地址可更换 Next…

年后找工作必看的自动化测试面试宝典,一般人我不告诉他

目录 前言 1.1 什么是 API&#xff1f; 1.2 什么是 API 测试&#xff1f; 1.3 常见的 API 测试类型有哪些&#xff1f; 1.4 列举 API 测试中使用的一些常用协议&#xff1f; 1.9API 常见测试有哪些&#xff1f; 1.10API 测试有哪些优势&#xff1f; 1.11API 测试中究竟…

【PHP 随记】—— Composer 安装项目以及项目的扩展

&#x1f449;总目录&#x1f448;\large\colorbox{skyblue}{&#x1f449;总目录&#x1f448;}&#x1f449;总目录&#x1f448;​ 文章目录1、Composer 安装项目① 项目安装示例② 相关问题解决③ 框架搜索指南2、Composer 安装项目的扩展使用 Composer 更轻松方便地安装 P…

RV1126笔记二十五:区域入侵检测

若该文为原创文章,转载请注明原文出处。 一、 前言 区域入侵检测是通过识别目标之后获取目标坐标位置,判断目标是否在所标定的区域内出现,常常被用在电子围栏,不安全区域入侵检测,智慧城市,安防监控等领域。具体使用场景有,在标定的区域内不能抽烟,进入工地区域必须佩…

Java快速上手Properties集合类

概念 Java中的Properties文件是一种配置文件&#xff0c;主要用于表达配置信息&#xff0c;格式是文本文件。该类主要用于读取Java的配置文件&#xff0c;也可以对properties文件进行修改 属性配置&#xff1a;以“键值”的方式书写一个属性的配置信息 注 释&#xff1a;在pro…

我建议大学生看一下阿凡达2,对离校后很有帮助

网上评价阿凡达2说剧情拉胯&#xff0c;但我却通过他的剧情看到了当代大学生的一些影子&#xff0c;尤其是对于离校的毕业生来说&#xff0c;相对吻合的场景还是很多的&#xff0c;让我来分析一下。 目录 阿发达2关键剧情点 1、在校期间 2、终于要离校了 3、离开学校&…

Flutter 安装踩坑记录 HTTP host https://pub.dev/ is not reachable.

Flutter安装踩坑记录安装Flutter SDK参考链接安装Flutter SDK Windows安装 flutter官网下载flutter sdk包 2.解压到C盘除去program file相关的目录&#xff08;最好自己创建一个新的目录&#xff0c;因为放在program file等目录需要特殊的权限&#xff0c;会出现问题&#xf…

【解读】CSA CISO研究报告: 零信任的部署现状及未来展望

零信任理念已经存在了十多年。然而对需要保护IT系统的企业来说&#xff0c;这个术语及其实施方式的关注度显著增加。 国际云安全联盟CSA发布调研报告《CISO研究报告&#xff1a;零信任的部署现状及未来展望》&#xff0c;本次调研的目的是使大家更好地理解组织机构内部的零信任…

【My Electronic Notes系列——放大电路与集成运算放大器】

目录 序言&#xff1a; &#x1f3c6;&#x1f3c6;人生在世&#xff0c;成功并非易事&#xff0c;他需要破茧而出的决心&#xff0c;他需要永不放弃的信念&#xff0c;他需要水滴石穿的坚持&#xff0c;他需要自强不息的勇气&#xff0c;他需要无畏无惧的凛然。要想成功&…

电脑怎么录制视频?如何录制页面上指定区域

你知道电脑怎么录制视频吗&#xff1f;有时候我们并不需要录制整个电脑屏幕&#xff0c;只需要对特定区域&#xff0c;这个时候该怎么办呢&#xff1f;其实我们只需要一款既支持全屏录制又支持区域录制的录屏工具&#xff0c;就可以轻松搞定。下面小编教您如何录制电脑上指定的…

azg携手Bubs出席2022未来母婴大会主题专场,探讨母婴品牌长红发展路径

近日&#xff0c;由母婴行业观察主办的“2022第八届未来母婴大会”在上海成功举办&#xff0c;来自行业内的近百位资深嘉宾受邀参与&#xff0c;共襄盛会。而在以“新周期 新机遇&#xff0c;母婴品牌可持续生长路径”的2022未来母婴大会主题专场中&#xff0c;Bubs中国区资深渠…

梦熊杯-十二月月赛-白银组题解-D.智慧

D. Problem D.智慧&#xff08;wisdom.cpp&#xff09; 内存限制&#xff1a;256 MiB 时间限制&#xff1a;1000 ms 标准输入输出 题目类型&#xff1a;传统 评测方式&#xff1a;文本比较 题目描述: 「须弥」是「智慧」的国度。 布耶尔认为&#xff0c;如果能只使用加…

CTFshow--web--红包题第七弹

好家伙&#xff0c;一打开就是个phpinfo&#xff0c;看看了感觉暂时没有什么利用的东西&#xff0c;扫一下目录吧使用dirsearch扫描&#xff0c;发现/.git/index的一个目录文件&#xff0c;访问下载他python dirsearch.py -u "http://98c1d213-ce9d-4415-99b1-2b4f2c80430…

java 探花交友项目day5 推荐好友列表 MongoDB集群 发布动态,查询动态 圈子功能

推荐好友列表 需求分析 推荐好友&#xff1a;分页形式查询推荐的用户列表&#xff0c;根据评分排序显示 代码实现&#xff1a; tanhuaController: /** * 查询分页推荐好友列表 */ GetMapping("/recommendation") public ResponseEntity recommendation(Recomme…

回归模型评价指标原理与基于sklearn的实现

1 前言 回归任务是机器学习中常见的任务&#xff0c;特别是涉及到具体的发电量预测、风力预测等工业任务时&#xff0c;有非常多的应用场景。回归任务不同于分类任务&#xff0c;回归任务的预测值一般是连续的数&#xff0c;分类任务的预测值则是离散的值&#xff08;比如0、1分…

ssh2.js+Shell一套组合拳下来,一年要花2080分钟做的工作竟然节省到52分钟~

前言 进入了新的一年&#xff0c;团队被分配了新的工作内容——每周巡检。 巡检工作简单&#xff0c;但需要人工重复性地登陆远程服务器、输入重复的命令&#xff0c;然后将命令的结果记录下来。每做一次估计花40分钟&#xff0c;但要每周做&#xff0c;一年52周&#xff0c;…