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

news2024/10/7 10:18:34

一、前置知识 — 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 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 变化
 * 用 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 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 变化
 * 用 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>

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

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

相关文章

实时翻译器-实时自动翻译器

自动翻译器——让语言不再是障碍。 在当今全球化的背景下&#xff0c;语言已不再是跨文化交流的障碍。而自动翻译技术作为突破语言壁垒的有效手段&#xff0c;越来越受到关注和需求。我们的自动翻译器就是一个高效、准确的翻译工具&#xff0c;它能够根据用户输入的内容自动识…

mysql知识点看这一篇就够了!

存储引擎 InnoDB InnoDB 是 MySQL 默认的事务型存储引擎&#xff0c;只要在需要它不支持的特性时&#xff0c;才考虑使用其他存储引擎。 InnoDB 采用 MVCC 来支持高并发&#xff0c;并且实现了四个标准隔离级别(未提交读、提交读、可重复读、可串行化)。其默认级别时可重复读…

Springboot项目怎么设计业务操作日志功能?

目录 前言 需求描述与分析 系统日志 操作日志 设计思路 Spring AOP Filter和HandlerInterceptor 过滤器 拦截器 SpringAOP、过滤器、拦截器对比 实现方案 环境配置 依赖配置 表结构设计 代码实现 测试 调试方法 验证结果 总结 前言 很久以前都想写这篇文章…

[ 应急响应基础篇 ] Windows系统隐藏账户详解(Windows留后门账号)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

Spring Web MVC DispatcherServlet详解—官方原版

一、概述 Spring Web MVC是基于Servlet API构建的原始Web框架&#xff0c;从一开始就包含在Spring框架中。正式名称“SpringWebMVC”来自其源模块&#xff08;Spring-webmvc&#xff09;的名称&#xff0c;但它更常见的名称是“SpringMVC”。 与Spring Web MVC并行&#xff0c…

【AIGC】GitHub Copilot 免费注册及在 PyCharm 中的安装使用

欢迎关注【youcans的 AIGC 学习笔记】原创作品 《GitHub Copilot 免费注册及在 VS Code 中的安装使用》 《GitHub Copilot 免费注册及在 PyCharm 中的安装使用》 GitHub Copilot 免费注册及在 PyCharm 中的安装使用1. GitHub Copilot 功能介绍2. 用户注册与申请2.1 个人订阅 Gi…

经典算法50例-无敌五十剑-算法五十重天

这里写目录标题1.汉诺塔2.费式数列3.巴斯卡三角形4.三色棋5.老鼠走迷官&#xff08;一&#xff09;6.老鼠走迷官&#xff08;二&#xff09;7.骑士走棋盘8.八皇后9.八枚银币10.生命游戏11.字串核对12.双色、三色河内塔13.背包问题14.蒙地卡罗法求 PI15.Eratosthenes筛选求质数1…

LeetCode——遍历序列构造二叉树

105从前序与中序遍历序列构造二叉树 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,2…

3.12、生成者消费者模型

3.12、生成者消费者模型1.生产者消费者模型介绍2.一个没有实现线程同步的生产者消费者模型1.生产者消费者模型介绍 生产者消费者模型是一种多线程的设计模式&#xff0c;用于解决生产者和消费者之间的同步和协作问题。 在生产者消费者模型中&#xff0c;生产者和消费者通过共享…

代码随想录算法训练营第五十五天 | 392.判断子序列、115.不同的子序列

打卡第55天。 今日任务 392.判断子序列115.不同的子序列 392.判断子序列 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xf…

哪个品牌的蓝牙耳机便宜耐用?内行公认四大便宜耐用的蓝牙耳机

蓝牙耳机发展至今&#xff0c;品牌众多&#xff0c;且各品牌旗下拥有无数不同价格的耳机&#xff0c;各自的主打优势又不一样。那么&#xff0c;哪个品牌的蓝牙耳机便宜耐用&#xff1f;下面&#xff0c;我来给大家推荐四款便宜耐用的蓝牙耳机&#xff0c;一起来看看吧。 一、…

【数据结构】第九站:树和二叉树

目录 一、树的概念及结构 1.树的概念 2.树的相关概念 3.树的表示 二、二叉树的概念及结构 1.概念 2.特殊的二叉树 3.二叉树的性质 三、二叉树的存储结构 一、树的概念及结构 1.树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个…

Redis缓冲区溢出及解决方案

缓冲区(buffer)&#xff0c;是内存空间的一部分。也就是说&#xff0c;在内存空间中预留了一定的存储空间&#xff0c;这些存储空间用来缓冲输入或输出的数据&#xff0c;这部分预留的空间就叫做缓冲区。 一、Redis缓冲区溢出影响 在Redis中&#xff0c;主要有三个场景用到了…

数据资产目录建设方法

以信息技术为核心的第四次经济革命使得全球经济进入到数字化转型时期&#xff0c;对于今天的企业来说&#xff0c; 数字化转型已经不是可做可不做的自选题&#xff0c; 而是必须付诸行动的必选题。 从数字化转型的实践经验中我们可以得知&#xff0c;企业的数据资产是企业数字…

Flink CDC 在京东的探索与实践

摘要&#xff1a;本文整理自京东资深技术专家韩飞&#xff0c;在 Flink Forward Asia 2022 数据集成专场的分享。本篇内容主要分为四个部分&#xff1a; 京东自研 CDC 介绍京东场景的 Flink CDC 优化业务案例未来规划点击查看直播回放和演讲 PPT 一、京东自研 CDC 介绍 京东自研…

pdf转换成word怎么转换?这个方法一学就会!

在日常工作和生活中&#xff0c;我们常常会遇到需要将PDF文件转换为Word文档的情况。这个过程有时候会令人感到麻烦和心累&#xff0c;需要特殊操作才能完成。但实际上&#xff0c;通过选择正确的方法&#xff0c;文件格式转换只需要几秒钟的时间。如果你感到不可思议&#xff…

ChatGPT-4 来了,附国内体验地址

chatgpt4是什么&#xff1f; 2022年12月&#xff0c;openAI发布了chatgpt模型&#xff0c;这个属于GPT-3.5系列模型中的一个。上个月&#xff0c;openAI又发布了超级升级版的GPT-4模型。所以&#xff0c;你想问的chatgpt4模型是指代GPT-4模型。 相比前一个版本&#xff0c;它…

智能硬件蓝牙配网方案概要

智能硬件开发系列 Google Protobuf 实践使用开发智能硬件蓝牙配网方案概要JNI开发必学C基础JNI开发必学C使用实践Android Studio 4.0.NDK项目开发详细教学Android NDK与JNI的区别有何不同&#xff1f;Android Studio 4.0.NDK .so库生成打包Android JNI的深度进阶学习Android S…

【Leetcode】题库-爽刷简单题(1)

目录 写在前面&#xff1a; 题目&#xff1a;67. 二进制求和 - 力扣&#xff08;Leetcode&#xff09; 解题思路&#xff1a; 代码&#xff1a; 过过过过过过啦&#xff01;&#xff01;&#xff01;&#xff01; 题目&#xff1a;83. 删除排序链表中的重复元素 - 力扣&a…

linux之jdk1.8环境安装与配置和Maven安装与配置

文章目录一、jdk1.8环境安装1、官网下载&#xff1a;<https://www.oracle.com/java/technologies/downloads/#java8>2、在usr文件夹下新建一个java文件夹3、解压完成后&#xff0c;将文件jdk文件传入到java目录下二、配置环境&#xff08;重点&#xff09;1、按 i 进行编…