当前位置: 首页 > news >正文

Vue3自定义指令之前端水印功能实现

一、前置知识 — Vue 中的自定义指令

先来说说 vue2和vue3中自定义全局指令的区别

相同点:指令的应用场景,原理是一致的;
不同点:生命周期钩子函数名,指令定义的格式不一样。

vue2中自定义全局指令:

  • 定义
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {// 当被绑定的元素插入到 DOM 中时……inserted: function (el) {// 聚焦元素el.focus()}
})
  • 使用
<input v-foucs="'xxxx'" />

vue3中自定义全局指令:

  • 定义
// app是Vue实例。 e.g:  app = createApp()
app.directive('指令名', {// 省略其他生命周期钩子函数// el: 使用了指令的dom// binding.value:  v-指令名="binding.value就是这里表达式的值"mounted (el, binding) {console.log(el, binding.value)}
})
  • 使用
<元素 v-指令名="xxx" />

扩展: 在vue3中以插件的格式定义全局指令

  • 定义
// 定义全局指令
export default {install (app) {// app是Vue实例。 e.g:  app = createApp()app.directive('指令名', {// 省略其他生命周期钩子函数// el: 使用了指令的dom// binding.value:  v-指令名="binding.value就是这里表达式的值"mounted (el, binding) {console.log(el, binding.value)}})}
}
  • 使用(在main.js中注册插件)
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'
import directives from './directives'createApp(App).use(router).use(directives).mount('#app')

二、实现水印功能

前端实现水印功能的方式有很多,这里主要讲解基于Canvas和MutationObserver的实现方式

1. 实现思路

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

2. 生成水印

  • 通过将图片绘制在canvas中,然后通过canvas的toDataURL方法,将图片转为base64编码.

备注: toDataURL用法

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

// 全局保存 canvas 和 div ,避免重复创建(单例模式)
const globalCanvas = null;
const globalWaterMark = null;// 返回一个包含图片展示的 数据URL
const getDataUrl = (binding: any) => {const rotate = -20;const canvas = globalCanvas || document.createElement("canvas");const ctx = canvas.getContext("2d"); // 获取canvas画布的绘图环境ctx?.rotate((rotate * Math.PI) / 180); // 水印旋转角度ctx.font = binding.font;ctx.fillStyle = binding.fillStyle;ctx?.fillText(binding.text || '机密文件', canvas.width / 3, canvas.height / 2);return canvas.toDataURL("image/png");
};

3. 使用MutationObserver监听水印

MutationObserver介绍

> 背景:

MutationObserver 接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。

> 构造函数:

MutationObserver() 创建并返回一个新的 MutationObserver 它会在指定的 DOM 发生变化时被调用。

> 调用方法

disconnect()

阻止 MutationObserver 实例继续接收的通知,直到再次调用其 observe() 方法,该观察者对象包含的回调函数都不会再被调用。

observe()

配置 MutationObserver 在 DOM 更改匹配给定选项时,通过其回调函数开始接收通知。

takeRecords()

从 MutationObserver 的通知队列中删除所有待处理的通知,并将它们返回到 MutationRecord 对象的新 Array 中。

MutationObserver具体监听逻辑如下:

(1)直接删除dom

  • 先获取设置水印的dom
  • 监听到被删除元素的dom
  • 如果他两相等的话就停止观察,初始化(设置水印+启动监控)

(2)删除style中的属性

  • 判断删除的是否是标签的属性 (type === “attributes”)
  • 判断删除的标签属性是否是在设置水印的标签上
  • 判断修改过的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 directives: any = {mounted(el: HTMLElement, binding: any) {// 注意img有onload的方法,如果自定义指令注册在html标签的话,只需要init(el, binding.value)el.onload = init.bind(null, el, binding);// init(el, binding);},
};// 初始化
const init = (el: HTMLElement, binding: any = {}) => {// 设置水印setWaterMark(el, binding.value);// 启动监控createObserver(el, binding.value);
};// 设置水印
const setWaterMark = (el: HTMLElement, binding: any = {}) => {const { parentElement } = el;// 获取对应的 canvas 画布相关的 base64 urlconst 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 变化* 用 MutationObserver 对水印元素进行监听,删除时,我们再立即生成一个水印元素就可以了* @param el* @param binding*/
const createObserver = (el: HTMLElement, binding: any) => {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");// 证明被删除了if (removedNodes[0] === waterMarkEl) {// 停止观察。调用该方法后,DOM 再发生变动,也不会触发观察器observer.disconnect();// 初始化(设置水印,启动监控)init(el, binding);} else if (type === "attributes" &&target === waterMarkEl &&currStyle !== style) {waterMarkEl.setAttribute("style", style);}}});observer.observe(el.parentElement, {childList: true,attributes: true,subtree: true,});
};

4、效果展示

在这里插入图片描述

在这里插入图片描述

三、查看基于Vue3 实现的完整代码

1. 目录结构

在这里插入图片描述

2. main.js

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'
import directives from './directives'createApp(App).use(router).use(directives).mount('#app')

3. App.vue

<script setup lang="ts">
import HelloWorld from './views/HelloWorld.vue';
</script><template><div><!-- <a href="https://vuejs.org/" target="_blank"><img src="./assets/vue.svg" class="logo vue" alt="Vue logo" /></a> --></div><div><router-view /></div>
</template><style scoped>
.logo {height: 6em;padding: 1.5em;will-change: filter;transition: filter 300ms;
}
.logo:hover {filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

4. directives

/directives/index.ts

import type { App } from 'vue'
import watermark from './waterMark'export default function installDirective(app: App) {app.directive(watermark.name, watermark.directives);
}

/directives/waterMark.ts


// 全局保存 canvas 和 div ,避免重复创建(单例模式)
const globalCanvas = null;
const globalWaterMark = null;// 返回一个包含图片展示的 数据URL
const getDataUrl = (binding: any) => {const rotate = -20;const canvas = globalCanvas || document.createElement("canvas");const ctx = canvas.getContext("2d"); // 获取canvas画布的绘图环境ctx?.rotate((rotate * Math.PI) / 180); // 水印旋转角度ctx.font = binding.font;ctx.fillStyle = binding.fillStyle;ctx?.fillText(binding.text || '机密文件', canvas.width / 3, canvas.height / 2);return canvas.toDataURL("image/png");
};// watermark 样式
let style = `
display: block;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-repeat: repeat;
pointer-events: none;`;// 定义指令配置项
const directives: any = {mounted(el: HTMLElement, binding: any) {// 注意img有onload的方法,如果自定义指令注册在html标签的话,只需要init(el, binding.value)// el.onload = init.bind(null, el, binding);init(el, binding);},
};// 初始化
const init = (el: HTMLElement, binding: any = {}) => {// 设置水印setWaterMark(el, binding.value);// 启动监控createObserver(el, binding.value);
};// 设置水印
const setWaterMark = (el: HTMLElement, binding: any = {}) => {const { parentElement } = el;// 获取对应的 canvas 画布相关的 base64 urlconst 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 变化* 用 MutationObserver 对水印元素进行监听,删除时,我们再立即生成一个水印元素就可以了* @param el* @param binding*/
const createObserver = (el: HTMLElement, binding: any) => {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");// 证明被删除了if (removedNodes[0] === waterMarkEl) {// 停止观察。调用该方法后,DOM 再发生变动,也不会触发观察器observer.disconnect();// 初始化(设置水印,启动监控)init(el, binding);} else if (type === "attributes" &&target === waterMarkEl &&currStyle !== style) {waterMarkEl.setAttribute("style", style);}}});observer.observe(el.parentElement, {childList: true,attributes: true,subtree: true,});
};export default {name: "watermark",directives,
};

5. router/index

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';const routes: Array<RouteRecordRaw> = [{path: '/',name: 'HelloWorld',component: () => import('../views/HelloWorld.vue'),},{path: '/watermark',name: 'Watermark',component: () => import('../views/Watermark.vue'),},]const router = createRouter({history: createWebHashHistory(),routes,})export default router;

6. views/Watermark.vue

<script setup lang="ts">
import { reactive } from 'vue';
// 定义对象
const watermarkObj = reactive({font: '16px normal',fillStyle: 'rgba(180, 180, 180, 0.3)',text: '机密专用',
});
</script>
<template><div class="content"><h2>出现水印</h2><!-- <div  class="content"><img v-watermark="watermarkObj" src="../assets/vue.svg" class="logo" />出现水印在 img 标签上</div> --><div v-watermark="watermarkObj">出现水印在 div 标签上</div></div>
</template>
<style>
.logo {height: 6em;padding: 1.5em;will-change: filter;transition: filter 300ms;display: none;
}
.content {width: 1000px;height: 900px;
}
</style>

相关文章:

Vue3自定义指令之前端水印功能实现

一、前置知识 — Vue 中的自定义指令 先来说说 vue2和vue3中自定义全局指令的区别 相同点&#xff1a;指令的应用场景&#xff0c;原理是一致的&#xff1b; 不同点&#xff1a;生命周期钩子函数名&#xff0c;指令定义的格式不一样。 vue2中自定义全局指令&#xff1a; 定义…...

文章生成器写出来的原创文章

文章生成机器人 文章生成机器人是一种基于人工智能技术和自然语言处理算法的程序&#xff0c;可以自动地生成高质量、原创的文章。 文章生成机器人的优点如下&#xff1a; 提高工作效率&#xff1a;文章生成机器人能够在较短的时间内自动帮助用户生成大量的文章&#xff0c;提…...

2023年全国最新高校辅导员精选真题及答案49

百分百题库提供高校辅导员考试试题、辅导员考试预测题、高校辅导员考试真题、辅导员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 76.气质就是我们平常所说的脾气秉性。 答案&#xff1a;正确 77.社会心理通常是通过社会…...

【STL十】关联容器——set容器、multiset容器

【STL十】关联容器——set容器、multiset容器一、set简介二、头文件三、模板类四、set的内部结构五、成员函数1、迭代器2、元素访问3、容量4、修改操作~~5、操作~~5、查找6、查看操作六、demo1、查找find2、修改操作insert3、修改操作erase、clear七、multisetset和multiset会根…...

什么是Node.js

文章目录什么是Node.js简介常用命令Node内置模块Node.js和JavaScript的区别什么是Node.js 简介 Node.js是一个基于Chrome V8引擎的JavaScript运行环境。它允许开发者使用JavaScript编写服务器端代码&#xff0c;而不仅仅是浏览器端的代码。Node.js的出现使得JavaScript可以在…...

比GPT-4 Office还炸裂,阿里版GPT全家桶来袭

疯狂3月的那一天&#xff0c;一切还历历在目。 微软突然在发布会上放出大招&#xff0c;用Microsoft 365 Copilot掀起了办公软件革命。 而今天&#xff0c;阿里也放出一枚重磅炸弹——阿里版的Copilot也要来了&#xff01; 并且比微软更彻底的是&#xff0c;阿里全系产品也都…...

mysql 建表约束

主键约束 -- 主键约束 -- 使某个字段不重复且不得为空&#xff0c;确保表内所有数据的唯一性。 CREATE TABLE user (id INT PRIMARY KEY,name VARCHAR(20) );-- 联合主键 -- 联合主键中的每个字段都不能为空&#xff0c;并且加起来不能和已设置的联合主键重复。 CREATE TABLE …...

在Vue项目中使用tinymce富文本编辑器

TinyMC编辑器简介 TinyMCE是一款易用、且功能强大的所见即所得的富文本编辑器。跟其他富文本编辑器相比&#xff0c;有着丰富的插件&#xff0c;支持多种语言&#xff0c;能够满足日常的业务需求并且免费。 TinyMCE的优势&#xff1a; 开源可商用&#xff0c;基于LGPL2.1 插…...

GPT-4 和ChatGPT API的定价分析

OpenAI发布了他们的ChatGPT新机器学习模型GPT-4。GPT-4是GPT-3的一大进步&#xff0c;GPT-3是当前ChatGPT免费版本(GPT 3.5 Turbo)所运行的模型的基础&#xff0c;今天我们也来凑个热点&#xff0c;研究一下它们的定价 GPT-4新的功能 GPT-4可以在对话中使用图像&#xff0c;并…...

基于html+css的盒子展示2

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…...

【持续更新篇】SLAM视觉特征点汇总+ORB特征点+VINS前端

Harris角点 opencv函数 cornerHarris提取输入图像的Harris角点 检测原理 检测思想&#xff1a;使用一个固定窗口在图像上进行任意方向的滑动&#xff0c;对比滑动前后的窗口中的像素灰度变化程度&#xff0c;如果存在任意方向上的滑动&#xff0c;都有较大灰度变化&#xf…...

【C语言】初阶指针(指针及其类型以及野指针)

简单不先于复杂&#xff0c;而是在复杂之后。 目录 1. 指针是什么&#xff1f; 2. 指针和指针类型 2.1 指针-整数 2.2 指针的解引用 3. 野指针 3.1 野指针成因 3.2 如何规避野指针 1. 指针是什么&#xff1f; 指针理解的两个要点&#xff1a; 1. 指针是内存中最小…...

UDS统一诊断服务【六】访问时序参数0X83服务

文章目录前言一、访问时序参数服务介绍二、数据格式2.1 请求报文2.2 子功能2.3 响应三、举例前言 本文介绍UDS统一诊断服务的访问时序参数0X83服务&#xff0c;希望能对你有所帮助 一、访问时序参数服务介绍 这个服务我目前在项目中没怎么用到过&#xff0c;先来看看ISO14229…...

Linux应用编程(文件属性与目录)

本章将会讨论如下主题内容。 ⚫ Linux 系统的文件类型&#xff1b; ⚫ stat 系统调用&#xff1b; ⚫ 文件各种属性介绍&#xff1a;文件属主、访问权限、时间戳&#xff1b; ⚫ 符号链接与硬链接&#xff1b; ⚫ 目录&#xff1b; ⚫ 删除文件与文件重命名。 一、Linux 系统中…...

第十四届蓝桥杯嵌入式详解

目录 第一部分 客观试题&#xff08;15 分&#xff09; 不定项选择&#xff08;1.5 分/题&#xff09; 第二部分 程序设计试题&#xff08;85 分&#xff09; 2.1 STM32CubeMX初始化配置 2.1.1 配置GPIO 2.1.2 配置ADC 2.1.3 配置RCC 2.1.4 配置定时器TIM 2.1.5 配置ADC1、AD…...

新建论文三线表模板,一键格式刷

论文三线表模板写在最前面①表设计&#xff0c;新建表格样式②三线表上下线③三线表标题线④设置表格居中⑤设置表头格式容易出错的步骤写在最前面 论文写完啦&#xff0c;准备调整格式 之前建模也是三线表&#xff0c;但只能基于该文档模板&#xff0c;所以重新设置一下。 如…...

攻防世界-web2(逆向加密算法)

打开链接是PHP源码 给了一串密文&#xff0c;并对这串密文进行了一系列操作加密&#xff0c;注释里说解密$miwen就是flag 在此我们先介绍一些PHP内置函数&#xff1a; strrev(string): 反转字符串 strlen(string): 返回字符串的长度 substr(string, start, length): 返回字符…...

C语言学习1--------Visual Studio集成开发环境的搭建

C语言学习1--------Visual Studio集成开发环境的搭建适合初学者适用集成开发环境下载 Visual Studio 2019安装 Visual Studio 2019安装工作负载为C自定义安装位置激活 Visual Studio适合初学者适用集成开发环境 建议初学者适用最新的——Visual Studio 2019为集成开发环境。 部…...

腾讯云轻量应用服务器搭建网站教程(WordPress为例)

腾讯云轻量应用服务器搭建WordPress网站教程&#xff0c;先安装WordPress应用镜像&#xff0c;然后远程连接轻量应用服务器获取WP用户名和密码&#xff0c;域名DNS解析到轻量服务器IP地址&#xff0c;登陆WordPress后台管理全过程&#xff0c;腾讯云百科来详细说下腾讯云轻量服…...

mac上的PCB设计软件现状

Altium Designer是一款商业化的电路板设计软件&#xff0c;目前没有Mac版本。但是&#xff0c;MacOS上有一些类似Altium Designer的电路板设计软件&#xff0c;以下是一些常用的软件&#xff1a; Eagle&#xff1a;Eagle是一款商业化的电路板设计软件&#xff0c;具有强大的功能…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建

制造业采购供应链管理是企业运营的核心环节&#xff0c;供应链协同管理在供应链上下游企业之间建立紧密的合作关系&#xff0c;通过信息共享、资源整合、业务协同等方式&#xff0c;实现供应链的全面管理和优化&#xff0c;提高供应链的效率和透明度&#xff0c;降低供应链的成…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名&#xff08;Class Name)2.协议名&#xff08;Protocol Name)3.方法名&#xff08;Method Name)4.属性名&#xff08;Property Name&#xff09;5.局部变量/实例变量&#xff08;Local / Instance Variables&…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代&#xff0c;情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现&#xff0c;消费者对内容的“有感”程度&#xff0c;正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...