自适应且不可删除的水印蒙层

news2024/11/14 20:20:41

目录

canvas自适应文字长度,旋转角度生成水印背景图

生成蒙层

禁止蒙层的删除和修改


canvas自适应文字长度,旋转角度生成水印背景图

 

  • 设置canvas字体大小后,通过ctx.measureText(text).width获取两行文字的宽度text1,text2,取最大宽度为文本框宽度textWidth
  • 设置两行文字间距,可得文本框高度:textHeight=2*fontsize+ space_line
  • 计算最小一个能够完全包裹旋转后文本的盒子宽高
    已知旋转角度为rotate=>得到弧度rad = (rotate*Math.pi) /180
    单个水印图平铺成为蒙层的背景图,space_x,space_y用于调整水印之间的间距
 function  drawWatermark(el, config = {}) {
      if (!el) return;
      // 默认配置
      let {
        text1 = '今天也要保持愉悦鸭~', //文本1
        text2 = '2022-12-07', // 文本2
        space_x = 0, // x轴间距 
        space_y = 0, // y轴间距
        space_line = 20, //两列文字的间距
        font = 'Microsoft JhengHei bold',
        fontSize = 40, // 字体
        color = 'rgba(22,22,22,1)', // 字色
        rotate = 30 // 倾斜度
      } = config;
      const canvas =  document.createElement('canvas');
      el.appendChild(canvas);
      const ctx = canvas .getContext('2d');
      ctx.font = fontSize + 'px ' + font; //设置好fontsize才能正确计算出文本宽度
      let tw1= ctx.measureText(text1).width;
      let tw2= ctx.measureText(text2).width;
      let textWidth = Math.max(tw1, tw2); //文本最长宽度为文本框宽度
      let textHeight = fontSize * 2 + space_line; //文本框高度为两个文本+行间距
      let rad  = (rotate * Math.PI) / 180; //角度转弧度
      let sin = Math.sin(rad ); 
      let cos = Math.cos(rad );
      let width = textWidth * cos + textHeight * sin + space_x * 2; (textWidth * cos + textHeight * sin)//为包裹住文本框的最小盒子宽度
      let height = textWidth * sin + textHeight * cos + space_y * 2;(textWidth * sin + textHeight * cos)//为包裹住文本框的最小盒子高度
      canvas.width = width;
      canvas.height = height;
      canvas.style.cssText = `width:${width}px;height:${height}px;display:none;`;
      ctx.translate(space_x , textWidth * sin + space_y );  // 移动旋转中心
      ctx.rotate((-1 * (rotate * Math.PI)) / 180); //旋转文本框
      ctx.fillStyle = color;
      ctx.textAlign = 'left';
      ctx.textBaseline = 'middle';
      ctx.font = fontSize + 'px ' + font;
      ctx.fillText(text1, (textWidth - tw1) / 2, 0.5 * fontSize);  //文本在文本框中居中显示
      ctx.fillText(text2, (textWidth - tw2) / 2, 1.5 * fontSize + space_line); //文本在文本框中居中显示
      return canvas.toDataURL('image/png');
    },

生成蒙层

在目标元素下添加一个相对定位的子元素,将水印图片平铺作为背景图。

禁止蒙层的删除和修改

  • 删除或移动element
  • 修改style
 function createMask(el) {
      //创建蒙层
      let $mask = document.createElement('div');
      //判断蒙层父元素是否有定位
      let position = window.getComputedStyle(el, null).position;
      if (position === 'static') {
        el.style.position = 'relative';
      }
      //设置蒙层样式
      let style = `visibility: visible !important;
        transform: translate(0,0)  !important;
        display: block !important;
        visibility: visible !important;
        width: 100% !important; 
        height: 100% !important;
        background-color: rgba(0, 0, 0, 0)!important;
        background-repeat: repeat !important;
        position: absolute !important;
        top: 0px !important;
        left: 0px !important;
        z-index: 999 !important; 
        background-image: url(${drawWatermark(el)}) !important`;
      $mask.setAttribute('style', style);
      //添加蒙层
      el.append($mask);
      // 创建MutationObserver
      el.observer = new MutationObserver((mutationRecord) => {
        //处理DOM
        mutationRecord.forEach((mutation) => {
          // 蒙层删除或者被移动到别处
          if (mutation.target === el && mutation.removedNodes[0] == $mask) {
            el.append($mask);
          } else if (mutation.target == $mask && mutation.attributeName === 'style') {
            // 蒙层被更改样式 在监听到蒙层样式更改后,赋值的新的样式会导致再次触发监听回调,所以需要在监听事件中判断何时需要赋值
            const changestyle = $mask?.getAttribute('style');
            if (changestyle !== style) {
              $mask.setAttribute('style', style);
            }
          }
        });
      });
      // 启动监控
      el.observer.observe(el, {
        childList: true,
        attributes: true,
        subtree: true
      });
      return $mask;
    },

行内样式加important是为了防止通过添加class或其他css覆盖样式(暂时没有找到怎么如何通过修改css的方式更改样式的监听方式)
踩得一个坑
设置元素的行内样式有很多种

方式二设置样式后,行内样式格式和赋值时的style的格式不一样,获取到行内style后直接进行===判断,回造成死循环

解决方法:

  • 第一次监听到蒙层更改时,立刻移除蒙层,重新生成新蒙层
  • 写个函数判断不同格式的两个样式属性上是否相等

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

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

相关文章

python-(6-5-1)爬虫---xpath解析实战

文章目录一 环境准备二 需求三 分析1 拿到页面源代码2 提取和解析数据四 步骤流程1 拿到页面源代码2 提取和解析数据五 完整代码xpath是在XML文档中搜索内容的一门语言 html是xml的一个子集 一 环境准备 安装lxml模块 二 需求 爬取某网站的数据 三 分析 1 拿到页面源代码 …

计算机领域热知识【2】消息队列与celery

Celery是实现消息队列的一个工具,本篇博客将介绍消息队列的基础知识,以及celery实现消息队列的总体方法。想要实现用Celery实现消息队列实例的朋友,可以从本篇博客中找到我写的另一篇介绍使用Celery和RabbitMQ实现消息队列的博客。 目录消息队…

Java+Swing+mysql天气信息管理系统

JavaSwingmysql天气信息管理系统一、系统介绍二、功能展示1.主要功能2.主页3.查询历史天气三、数据库四、其他系统一、系统介绍 该系统实现: 通过高德天气API查询天气数据 将查询的数据存入本地数据库 删除数据。 二、功能展示 1.主要功能 2.主页 3.查询历史天气 三、数据库…

Netty04——优化与源码

目录1. 优化1.1 扩展序列化算法1.2 参数调优1.2.1.CONNECT_TIMEOUT_MILLIS1.2.2.SO_BACKLOG1.2.3.ulimit -n1.2.4.TCP_NODELAY1.2.5.SO_SNDBUF & SO_RCVBUF1.2.6.ALLOCATOR1.2.7.RCVBUF_ALLOCATOR1.3 RPC 框架1.3.1.准备工作1.3.2.服务器 handler1.3.3.客户端代码第一版1.3…

C#车库信息管理系统

车库信息管理系统实现 技术 C# sqlserver 系统功能 基本的登录注册车库管理员进行车辆信息的添加,即给车库登记车辆信息管理员对登记信息进行修改管理员对登记信息进行删除管理员对登记信息进行查询管理员对登记的车辆进行进库,出库处理实时统计车库…

CS61A学习笔记 lecture1 Computer science

CS61A学习笔记 lecture1 Computer science SICP: Structure and Interpretation of Computer Programs 计算机程序的构造和解释 一开始其实是想做南大的SICP学习笔记的,但是没有找到南大这门课的视频,还有就是他是CS61A的clone,网上也有CS61A…

Qt 多线程之QtConcurrent::map(处理序列容器)

QtConcurrent::map()、QtConcurrent::mapped() 和 QtConcurrent::mappedReduced() 函数对一个序列中(例如:QList、QVector)的项目并行地进行计算。 1、map函数 map函数的功能是在其他线程运行指定的函数,map函数有两个参数 第一…

耗时一个月整理的,最新出炉的Java面试题合集(2022亲身经历)

面试题清单 个人近来面试了不少的公司的,该挂的挂,该应付通过的应付通过,目前对面试题部分做一个系统的总结。最起码要保证被问过的问题第二次被问到的时候是可以回答并且理解的。算是一个被动输入学习的过程。 题目持续更新,答…

xdma linux 驱动

一、下载XDMA文件 输入命令: sudo git clone https://github.com/Xilinx/dma_ip_drivers cd xx_dma/dma_ip_drivers/XDMA/linux-kernel/xdma$ 二 、编译: sudo make install 在最后会遇到下面这个问题: 三、添加key 文件 cd /lib/modules/5.4.0-135-generic/build/ce…

使用 EF Core 处理Sqlite数据库

使用 EF Core 处理Sqlite数据库 1.通过NuGet安装Microsoft.EntityFrameworkCore.Sqlite 2.编写生成数据库的实体类 因为EF Core是通过实体类来作为数据库的字段 public class User {/// <summary>/// 主键 Id/// </summary>[Key]public int Id { get; set; }///…

代码上传gitee

有两种场景&#xff1a; 一、自己的代码没有用git管理&#xff0c;先将自己的代码用git管理起来&#xff0c;然后上传gitee gitee注册账号后&#xff0c;点“”号创建代码仓库&#xff0c;这就是你的代码库上传后的路径。 然后填写仓库名称和路径&#xff0c;这个可以随便写&…

【论文合集】2022年10月医学影像期刊论文合集

★ 本月IEEE Transactions on Medical Imaging(1区 top if 11.037) 共32篇, Medical Image Analysis&#xff08;1区 top if 13.828&#xff09; 共30篇. ”标题高频词汇 (segmentation, 13), (brain, 9), (mri, 6), (graph, 4)(attention, 4), (3d, 4), (contrastive, 4), …

代理模式--【学习笔记】

什么是代理模式&#xff1f; 代理是一种模式,提供了对目标对象的间接访问方式,即通过代理对象访问目标对象.如此便于在目标实现的基础上增加额外的功能操作,以满足自身的业务需求. 代理模式又分为静态代理&#xff0c;动态代理 静态代理模式 编写代理类, 要求: 代理类与目标类…

【我爱世界杯】伪球迷眼里的世界杯

大家好&#xff0c;我是【架构师李肯】&#xff0c;一个专注于嵌入式物联网架构设计的攻城狮。 文章目录按理说聊一聊我和足球第一次热衷于关注世界杯后ying情时代的卡塔尔世界杯祝愿世界杯按理说 嗯&#xff0c;按理说&#xff0c;我一个程序猿&#xff0c;既不踢球&#xff…

ChatGPT原理解析-张俊林

本文将从以下几个方面展开&#xff1a; 引言 ChatGPT的技术原理 引言 作为智能对话系统&#xff0c;ChatGPT最近两天爆火&#xff0c;都火出技术圈了&#xff0c;网上到处都在转ChatGPT相关的内容和测试例子&#xff0c;效果确实很震撼。我记得上一次能引起如此轰动的AI技术…

湃睿PMDS-Fx传感器在电动牙刷上的应用

电动牙刷、冲牙器等产品市场的爆发性增长&#xff0c;显示全球人口正在越来越关注牙齿/口腔的健康问题。 根据资料显示&#xff0c;中国电动牙刷市场规模呈现逐年上涨的态势&#xff0c;2017年中国电动牙刷市场规模为43亿元&#xff0c;2021年中国电动牙刷市场规模上涨为125亿…

01-go基础-10-结构体 struct (定义结构体、声明结构体变量、结构体赋值、结构体做参数、结构体指针、结构体嵌套、结构体打印)

文章目录1. 定义结构体类型2. 声明结构体变量3. 赋值3.1 用结构体赋值3.2 每个成员分别赋值4. 结构体使用4.1 结构体作参数4.2 结构体指针做参数4.3 二者区别4.4 本质原因5. 结构体嵌套5.1 一个结构体作为另一个结构体的成员1&#xff09;定义2&#xff09;赋值和引用3&#xf…

RabbitMQ的学习

MQ引言 什么是MQ MQ(Message Quene)&#xff1a;翻译为消息队列&#xff0c;通过典型的生产者和消费者模型生产者不断向消息队列中生产消息&#xff0c;消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的&#xff0c;而且只关心消息的发送和接收&#xff0c;没有…

【MySQL 原理篇】- 凭这个,我拿下字节面试

若是想查看原图&#xff0c;请点击这里 刘卡卡 | ProcessOn 超链接 索引 从存储结构上看&#xff0c;有哪些索引从存储结构上来划分&#xff1a;BTree索引&#xff08;B-Tree或BTree索引&#xff09;&#xff0c;Hash索引&#xff0c;full-index全文索引&#xff0c;R-Tree索…

[附源码]Python计算机毕业设计Django在线票务系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…