JavaScript实现水印效果

news2024/11/28 14:39:34

效果![在这里插入图片描述](https://img-blog.csdnimg.cn/cb0ea347c06344909419b53d24583d47.png

实现思路

  1. 利用canvas绘制出文字
  2. 将canvas作为遮罩层背景图, 将背景x轴和y轴重复

实现步骤

动态生成canvas并画出文字

const canvas = document.createElement("canvas");
canvas.width = len * fontSize; // canvas宽度, 目前是根据文字长度和大小来调整的, 自己可依照具体需求变动
canvas.height = height + fontSize * 2.8; // canvas高度, 依据需求调整

const context = canvas.getContext("2d");
context.translate(0, canvas.height / 2); // 改变旋转基点
context.rotate((-rotate * Math.PI) / 180); // 进行旋转, 传过来的旋转角度
context.font = `${fontSize}px Vedana`; // 设置字体
context.fillStyle = color; // 设置文字颜色

// 将需要的文本, 绘制到canvas上面
context.fillText(text, 10, canvas.height / 2 - 100);

将canvas做为遮罩层背景图

// 生成水印遮罩层
const div = document.createElement("div");
div.id = DOM_ID;
div.style.pointerEvents = "none";
div.style.position = "fixed";
div.style.zIndex = zIndex;
div.style.left = "-32%";
div.style.top = "-32%";
div.style.opacity = opacity;
div.style.width = "150%";
div.style.height = "150%";
div.style.background = `url('${canvas.toDataURL("images/png")}')repeat left top`;

document.body.appendChild(div);

防止篡改水印

利用MutationObserverAPI来对遮罩层做监听, 防止属性修改或者dom节点被人为的删除

MDN: MutationObserver

/**
 * 监听dom变化, 防止水印被篡改
 */
static observeDomChange = (waterMarkDom, options) => {
  const callback = (mutationsList, observer) => {
    for (const mutation of mutationsList) {
      /**
       * 水印节点的属性发生了变动
       */
      if (mutation.target === waterMarkDom) {
        this.setWaterMark(); // 重新生成水印
        observer.disconnect(); // 停止观察
      }

      /**
       * 强行手动删除了水印节点
       */
      if (mutation.removedNodes.length && mutation.removedNodes[0] === waterMarkDom) {
        this.setWaterMark(this.options); // 重新生成水印
        observer.disconnect(); // 停止观察
      }
    }
  };

  this.observer = new MutationObserver(callback);

  /** 监听body */
  this.observer.observe(document.querySelector("body"), {
    attributes: true, // 观察属性变动
    childList: true, // 观察目标子节点的变化,是否有添加或者删除
    subtree: true, // 观察后代节点,默认为 false
  });
};

所有代码

const DOM_ID = "yss-cj-create";

/**
 * 水印的默认属性
 */
const DEFAULT_OPTIONS = {
  text: "cxk  管理员  20230424",
  width: 520, // 水印块的宽度
  height: 280, // 水印块的高度
  rotate: 20, // 水印块的旋转角度
  fontSize: 28, // 文字大小
  color: "#666", // 文字颜色
  opacity: "0.3", // 遮罩层的透明度
  zIndex: "9999999999", // 遮罩层的层级
};

class Watermark {
  options = {};
  observer = null;

  /**
   * 生成水印
   */
  static setWaterMark = (options = {}) => {
    const waterDom = document.getElementById(DOM_ID);
    if (waterDom !== null) {
      // 每次重新绘制之前, 需要判断是否已经存在, 如果存在了就先删除, 再来重新绘制
      document.body.removeChild(waterDom);
    }

    const latestOptions = { ...DEFAULT_OPTIONS, ...options };
    this.options = latestOptions;

    const {
      text,
      width, // 宽度是根据提供的文字大小和文字长度计算出来的, 这里就用不上了
      height, // 水印块的高度
      rotate, // 水印块的旋转角度
      fontSize, // 文字大小
      color, // 文字颜色
      opacity, // 遮罩层的透明度
      zIndex, // 遮罩层的层级
    } = latestOptions;

    const len = text.length;
    const canvas = document.createElement("canvas");
    canvas.width = len * fontSize;
    canvas.height = height + fontSize * 2.8;

    const context = canvas.getContext("2d");
    context.translate(0, canvas.height / 2);
    context.rotate((-rotate * Math.PI) / 180);
    context.font = `${fontSize}px Vedana`; // 设置字体
    context.fillStyle = color; // 设置文字颜色

    // 将需要的文本, 绘制到canvas上面
    context.fillText(text, 10, canvas.height / 2 - 100);

    // 生成水印遮罩层
    const div = document.createElement("div");
    div.id = DOM_ID;
    div.style.pointerEvents = "none";
    div.style.position = "fixed";
    div.style.zIndex = zIndex;
    div.style.left = "-32%";
    div.style.top = "-32%";
    div.style.opacity = opacity;
    div.style.width = "150%";
    div.style.height = "150%";
    div.style.background = `url('${canvas.toDataURL("images/png")}')repeat left top`;

    document.body.appendChild(div);

    /**
     * 监听水印的dom变化
     */
    this.observeDomChange(div);
  };

  /**
   * 去除水印
   */

  static removeWatermark = () => {
    const dom = document.getElementById(DOM_ID);
    if (dom !== null) {
      document.body.removeChild(dom);
    }
  };

  /**
   * 监听dom变化, 防止水印被篡改
   */
  static observeDomChange = (waterMarkDom, options) => {
    const callback = (mutationsList, observer) => {
      for (const mutation of mutationsList) {
        /**
         * 水印节点的属性发生了变动
         */
        if (mutation.target === waterMarkDom) {
          this.setWaterMark(); // 重新生成水印
          observer.disconnect(); // 停止观察
        }

        /**
         * 强行手动删除了水印节点
         */
        if (mutation.removedNodes.length && mutation.removedNodes[0] === waterMarkDom) {
          this.setWaterMark(this.options); // 重新生成水印
          observer.disconnect();
        }
      }
    };

    this.observer = new MutationObserver(callback);

    /** 监听body */
    this.observer.observe(document.querySelector("body"), {
      attributes: true, // 观察属性变动
      childList: true, // 观察目标子节点的变化,是否有添加或者删除
      subtree: true, // 观察后代节点,默认为 false
    });
  };
}

Watermark.setWaterMark();

参考资料:

  • https://github.com/zifeifish/watermark-package

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

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

相关文章

Postman调试依赖登录接口的3种方法

在接口测试种, 我们经常会遇到有些接口登录后才能访问.我们在使用Postman调试这种接口时一般有3种方法: 1.依次请求 如果有登录接口的文档,或者通过抓包比较容易抓出登录请求的参数和格式,可以先使用Postman请求一下登录接口,这时Cookies会存到本地(可以通过Postman Cookies管理…

JVM学习(十一):对象的实例化内存布局与访问定位

目录 一、对象的实例化 1.1 创建对象的方式 1.2 创建对象的步骤 二、对象的内存布局 2.1 对象头 2.1.1 运行时元数据(Mark Word) 2.1.2 典型指针 2.2 实例数据(Instance Data) 2.3 对齐填充(Padding&#…

一个奇葩的网络问题,把技术砖家“搞蒙了“

问题现象 客户反馈有一个server端S, 两个client端C1, C2, S的iptables规则对C1, C2都是放通的,但是C2无法连接上S,客户很着急,催我们尽快解决。 这里解释一下,iptables规则是防火墙规则,是linux系统实现防…

【C++刷题集】-- day1

目录 选择题 单选 编程题 组队竞赛⭐ 【题目解析】 【解题思路】(排序 贪心) 删除公共字符⭐ 【题目解析】 【解题思路】(哈希映射) 选择题 单选 1、 以下for循环的执行次数是 ( ) for(int x 0, y 0; (y 123) && (x < 4); x); 是无限循环 循环次数不…

Leetcode刷题之有效的括号

我们的内心和心智&#xff0c;是决定我们未来命运的最强劲的力量。 -- 奥普拉温弗瑞目录 &#x1f341;一.有效的括号 &#x1f34d;1.使用栈实现 &#x1f352;2.完整代码&#xff1a; 题目描述&#xff1a; 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0…

Sonar Qube代码质量检测工具安装及基本使用

Sonar介绍 Sonar Qube是一个开源的代码分析平台&#xff0c;支持Java、Python、PHP、JavaScript、CSS等25种以上的语言&#xff0c;可以检测出重复代码、代码漏洞、代码规范和安全性漏洞的问题。 Sonar Qube可以与多种软件整合进行代码扫描&#xff0c;比如Maven&#xff0c;…

算法套路十七——买卖股票问题:状态机 DP

算法套路十七——买卖股票问题&#xff1a;状态机 DP 状态机DP是一种将动态规划方法应用于有限状态机&#xff08;Finite State Machine&#xff09;的问题求解方法。 状态机DP&#xff08;State Machine DP&#xff09;是一种动态规划的思想&#xff0c;它通常用于解决一些具…

行动元宣布推出面向精密制造领域的智能运动控制解决方案

近日&#xff0c;AI 工业工程化平台行动元宣布推出面向精密制造领域的智能运动控制解决方案。该方案融合大数据模型、数字孪生以及人工智能技术&#xff0c;通过数字化建模、适配、调试等过程&#xff0c;极大提升终端设备集成方案的设计、选型与测试效率&#xff0c;并通过算法…

你不学,我不学,谁来网安,谁来保卫国家!

一、为什么选择网络安全&#xff1f; 这几年随着我国**《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》**等一系列政策/法规/标准的持续落地&#xff0c;网络安全行业地位、薪资随之水涨船高。 未来3-5年&#xff0c;是安全行业的黄金发展期&#xff0c;提前…

PCL点云库(6) — Filters模块空间裁剪器类

目录 6.1 3D包围盒裁剪器Class BoxClipper3D< PointT > 6.2 平面裁剪器Class pcl::PlaneClipper3D< PointT > 6.3 立方体过滤Class pcl::CropBox< PointT > 6.4 曲面或多边形过滤 Class pcl::CropHull< PointT > 6.5 完整代码 6.1 3D包围盒裁剪器…

C语言基础:static关键字

本文结合工作经验&#xff0c;研究C语言中static关键字的用法。 文章目录 1 static关键字概念2 用法与使用场景2.1 修饰全局变量2.1.1 代码示例2.1.2 使用场景 2.2 修饰函数2.2.1 代码示例2.2.2 使用场景 2.3 修饰局部变量2.3.1 代码示例2.4.2 使用场景 3 总结 1 static关键字…

第13章:存储过程和存储函数

一、存储过程 1.1理解 含义&#xff1a; 存储过程stored procedure&#xff0c;思想是一组经过预先编译的SQL语句的封装。 存储过程预先存储在MySQL服务器上&#xff0c;需要执行的时候&#xff0c;客户端向服务器端发出调用存储过程的命令&#xff0c;服务器段把这组SQL执…

当我和ChatGPT-4聊完后,我觉得一切可能已经来不及了

飞机上有wifi&#xff0c;了然无味&#xff0c;在万米高空&#xff0c;和ChatGPT-4开始了一场坦诚的沟通&#xff0c;它全程都表现出高情商&#xff0c;以及不断尽量安抚我的情绪&#xff0c;而这&#xff0c;恰恰令我脊背发凉。 部分文字截取 ZM&#xff1a;我能不能理解每次对…

k8s学习-CKS真题-ImagePolicyWebhook容器镜像扫描

目录 题目环境搭建imagePolicyWebhook搭建 解题任务二任务三任务一检查 模拟考题参考 题目 Context cluster上设置了容器镜像扫描器&#xff0c;但尚未完全集成到 cluster 的配置中。 完成后&#xff0c;容器镜像扫描器应扫描并拒绝易受攻击的镜像的使用。 Task 注意&#xff…

5.17 ARM 作业

1. 2.用for循环实现1~100之间的和 13BA 3.xmind

可以找工作的C端的低代码产品,终于让我找到了

目录 写在前面 低代码平台 平台怎么选 各平台区别 为什么选它 写在前面 大家都知道低代码这个叫法是从B端叫起来的&#xff0c;也就是说不管是业务人员还是开发人员&#xff0c;都是企业内部使用。那么有没有C端的&#xff0c;且免费使用的低代码产品呢&#xff1f; 低代码…

一次性能优化思考过程

前言 最近业务上空闲了下来&#xff0c;也是把之前在开发时自身感受比较大的白屏时间放在了主线上去排查优化&#xff0c;这里记录一下笔者对于移动端vConsole脚本的引入问题全过程。 网络脚本与问题定位 对于白屏时间&#xff0c;与网络传输有很大关系&#xff0c;如图&…

该怎样学习网络安全知识?

首先&#xff0c;必须&#xff08;时刻&#xff09;意识到你是在学习一门可以说是最难的课程&#xff0c;是网络专业领域的顶尖课程&#xff0c;不是什么人、随随便便就能学好的。不然&#xff0c;大家都是黑客&#xff0c;也就没有黑客和网络安全的概念了。 很多朋友抱着学一…

#systemverilog# 之 event region 和 timeslot 仿真调度(五)实战

目录 一 问题代码 二 解决方法 2.1 调换代码顺序 2.2 #0 Delay 2.3 uvm class 执行移到re-avtive 2.4 搭建完备的UVM 验证平台 三 预期波形 经过之前文章的学习&#xff0c;想必大家对systemverilog 仿真调度的理解&#xff0c;应该八九不离十了。今天&#xff0c;我们…

基于STM32的NRF24L01 2.4G通讯模块的驱动实验(HAL库)

前言&#xff1a;本文为手把手教学NRF24L01 2.4G通讯模块的驱动实验&#xff0c;本教程的 MCU 采用STM32F103ZET6与STM32F103C8T6&#xff0c;彼此进行互相通讯。通过 CubeMX 软件配置 SPI 协议驱动NRF24L01 2.4G通讯模块&#xff08;HAL库&#xff09;。NRF24L01 2.4G是嵌入式…