重新认识下网页水印

news2024/11/15 8:47:46

使用背景图图片

在这里插入图片描述

单独使用 css 实现,使用 backgroundImage,backgroundRepeat 将背景图片平铺到需要加水印的容器中即可。
如果希望实现旋转效果,可以借助伪元素,将背景样式放到伪元素中,旋转伪元素实现:

<style>
 .watermark {
  position: relative;
  overflow: hidden;
  background-color: transparent;
 }
 .watermark::before {
  content: '';
  position: absolute;
  width: 160%;
  height: 160%;
  top: -20%;
  left: -20%;
  z-index: -1;
  background-image: url('./watermark.png');
  background-position: 0 0;
  background-origin: content-box;
  background-attachment: scroll;
  transform: rotate(-20deg);
  background-size: auto;
  background-repeat: round;
  opacity: 0.3;
  pointer-events: none;
 }
</style>

动态生成div

在这里插入图片描述

根据水印容器的大小动态生成div,div内可以任意设置文本样式和图片,借助userSelect禁止用户选中文本水印;

const addDivWaterMark = (el, text) => {
  const { clientWidth, clientHeight } = el;
  const waterWrapper = document.createElement('div');
  waterWrapper.className = "waterWrapper";
  const column = Math.ceil(clientWidth / 100);
  const rows = Math.ceil(clientHeight / 100);
  // 根据容器宽高动态生成div
  for (let i = 0; i < column * rows; i++) {
    const wrap = document.createElement('div');
    wrap.className = "water";
    wrap.innerHTML = `<div class="water-item">${text}</div>`
    waterWrapper.appendChild(wrap)
  }
  el.append(waterWrapper)
}

Canvas写入图片做背景水印

在这里插入图片描述

将图片写入Canvas然后将Canvas作为背景图

  const img = new Image();
  const { ctx, canvas } = createWaterMark(config);
  img.onload = function () {
    ctx.globalAlpha = 0.2;
    ctx.rotate(Math.PI / 180 * 20);
    ctx.drawImage(img, 0, 16, 180, 100);
    canvasRef.value.style.backgroundImage = `url(${canvas.toDataURL()})`
  };
  img.src = ImageBg;

Canvas写入文字做背景水印

在这里插入图片描述

将文字写入Canvas然后将Canvas作为背景图

 const canvas = document.createElement('canvas');
 canvas.width = width;
 canvas.height = height;
 const ctx = canvas.getContext('2d');
 ctx.clearRect(0, 0, width, height);
 ctx.fillStyle = fillStyle;
 ctx.globalAlpha = opacity;
 ctx.font = font
 ctx.rotate(Math.PI / 180 * rotate);
 ctx.fillText(text, 0, 50);
 return canvas

Svg做水印

在这里插入图片描述

通过svg样式来控制水印样式,再将svg转换成base64的背景图

  const svgStr =
    `<svg xmlns="http://www.w3.org/2000/svg" width="180px" height="100px">
      <text x="0px" y="30px" dy="16px"
      text-anchor="start"
      stroke="#000"
      stroke-opacity="0.1"
      fill="none"
      transform="rotate(-20)"
      font-weight="100"
      font-size="16"> 前端小书童</text>
    </svg>`;
  return `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgStr)))}`;

shadowDom水印

在这里插入图片描述

使用customElements自定义个一个标签(可以使用其他任意标签,不过注意shadow DOM会使起同级的元素不显示。)
可以像shadow DOM写入style样式和水印节点(可以使用背景或者div形式)
shadow DOM内部实现的样式隔离不用担心写入的style影响页面其他元素样式,这个特性在微前端的实现中也被广泛使用。

 class ShadowMark extends HTMLElement {
  constructor() {
   super();
   const shadowRoot = this.attachShadow({ mode: 'open' });
   const wrapContainer = document.createElement('div')
   const style = document.createElement('style');
   style.textContent = `
   .wrapContainer {
    width: 100%;
    height: 100%;
    display: flex;
    flex-wrap: wrap;
   }
   .watermark-item {
    display: flex;
    font-size: 16px;
    opacity: .3;
    transform: rotate(-20deg);
    user-select: none;
    white-space: nowrap;
    justify-content: center;
    align-items: center;
   }`;
   const waterHeight = 100
   const waterWidth = 100
   const { clientWidth, clientHeight } = document.querySelector('.shadow-watermark')
   const column = Math.ceil(clientWidth / waterWidth)
   const rows = Math.ceil(clientHeight / waterHeight)
   wrapContainer.setAttribute('class', "wrapContainer")
   for (let i = 0; i < column * rows; i++) {
    const wrap = document.createElement('div')
    wrap.setAttribute('class', 'watermark-item')
    wrap.style.width = waterWidth + 'px'
    wrap.style.height = waterHeight + 'px'
    wrap.textContent = "前端小书童"
    wrapContainer.appendChild(wrap)
   }
   shadowRoot.appendChild(style);
   shadowRoot.appendChild(wrapContainer)
  }
 }
 customElements.define('shadow-mark', ShadowMark);

盲水印

canvas画布(canvas.getContext(‘2d’))调用 getImageData 得到一个 ArrayBuffer,用于记录画布每个像素的 rgba 值

r: Red取值范围0~255
g: Green取值范围0~255
b:Blue取值范围0~255
a:Alpha 透明度取值范围0~1,0代表全透明
可以理解为每个像素都是通过红、绿、蓝三个颜色金额透明度来合成颜色

方案一:低透明度方案的暗水印

在这里插入图片描述

当把水印内容的透明度 opacity 设置很低时,视觉上基本无法看到水印内容,但是通过修改画布的 rgba 值,可以使水印内容显示出来。
选择固定的一个色值例如R,判断画布R值的奇偶,将其重置为0或者255,低透明的内容就便可以显示出来了。

const decode = (canvas, colorKey, flag, otherColorValue) => {
 const ctx = canvas.getContext('2d');
 const originalData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
 let data = originalData.data;
 for (let i = 0; i < data.length; i++) {
  //筛选每个像素点的R值
  if (i % 4 == colorKey) {
   if (data[i] % 2 == 0) {
    //如果色值为偶数
    data[i] = flag ? 255 : 0;
   } else {
    //如果色值为奇数
    data[i] = flag ? 0 : 255;
   }
  } else if (i % 4 == 3) {
   //透明度不作处理
   continue;
  } else {
   // 关闭其他色值
   if (otherColorValue !== undefined) {
    data[i] = otherColorValue
   }
  }
 }
 ctx.putImageData(originalData, 0, 0);
}

方案二:将水印内容以像素偏差记录到画布中

在这里插入图片描述

用画布和水印后的画布绘制的像素进行ArrayBuffer对比,在存在水印像素的位置(水印画布透明度不为0)修改图片画布的奇偶,这样通过上面指定色值和奇偶去解码时,修改的文本像素就会被显示出来;

const encode = (ctx, textData, color, originalData) => {
 for (let i = 0; i < originalData.data.length; i++) {
  // 只处理目标色值
  if (i % 4 == color) {
   // 水印画布透明度为0
   if (textData[i + offset] === 0 && (originalData.data[i] % 2 === 1)) {
    // 放置越界
    if (originalData.data[i] === 255) {
     originalData.data[i]--;
    } else {
     originalData.data[i]++;
    }
    // 水印画布透明度不为0
   } else if (textData[i + offset] !== 0 && (originalData.data[i] % 2 === 0)) {
    originalData.data[i]++;
   }
  }
 }
 ctx.putImageData(originalData, 0, 0);
}

方案三:数字加密

在图像信号的频域(变换域)中隐藏信息要比在空间域(上面得到的像素颜色的ArrayBuffer)中隐藏信息具有更好的防攻击性。
这部分暗水印的实现,可以直接使用阿里云提供给的api,不过需要图片资源藏到的阿里云的OSS下;

MutationObserver

可以发现上面水印基本都是通过增加节点或者背景图的形式来实现,那用户其实可以通过屏蔽样式或者删除Dom来消除水印,那么我们可以借用MutationObserver来监听下水印dom的变化,来阻止用户以这种形式来消除水印;

以上代码见:https://github.com/wenjuGao/watermark-demo

线上效果:https://watermark-demo.vercel.app/

参考:

https://www.cnblogs.com/88223100/p/Exploring-Web-Watermarking-Technology.html

https://blog.csdn.net/blueblueskyhua/article/details/120346195

https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API

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

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

相关文章

CHAPTER 5 文件共享 - FTP

文件共享 - FTP1 FTP1.1 传输方式1. ASCII传输方式2. 二进制传输模式3. 两种传输方式的区别1.2 支持的模式1. 主动模式(PORT)2. 被动模式(PASV)3. 如何选择4. 为什么绝大部分互联网应用都是被动模式&#xff1f;1.3 搭建FTP服务器&#xff08;使用vsftpd&#xff09;1. 安装软件…

计算机中信息的表示和处理 整数和小数的二进制表示

信息的表示和处理整数进制字移位运算无符号数和有符号数加法运算小数定点表示IEEE 浮点表示规格化和非规格化舍入浮点运算现代计算机存储和处理的信息以二值信号表示&#xff0c;这些二进制数字称为位&#xff0c;为什么要用二进制来进行编码&#xff1f;因为二进制只有1和0两种…

信捷 XDH Ethercat A_STOP指令

本指令使运动中的轴进行减速停止/急停。最常见的情况是用来停止以指定速度运行的轴。只要在运动&#xff0c;都可以用本指令停止。上图中&#xff0c;在M150的上升沿&#xff0c;执行A_STOP指令。A_STOP HD150 D150 M151 K0HD150--输入参数起始地址&#xff0c;HD158--输入参数…

创宇猎幽APT流量监测系统获CSTC年度网络安全优秀案例

近日&#xff0c;由中国软件评测中心发起的“2022 年度网络安全和数据安全优秀案例评选”活动评选结果正式公布。在众多参与案例中&#xff0c;创宇猎幽APT流量监测系统&#xff08;NDR&#xff09;凭借出色的行业场景实践和出众的产品表现力脱颖而出&#xff0c;成功入选“202…

5、score diffusion model

DDIM中遇到的Score-based SDE这里采用表示神经网络的预测值&#xff0c;用表示。同时等价于DDPM回顾贝叶斯公式原始公式前向过程表示连乘反向过程由前向过程可知&#xff1a;所以&#xff1a;正态分布&#xff1a;重参数技巧&#xff1a;反向过程&#xff1a;优化目标&#xff…

ASP.NETCore学习资料

1.ASP.NETCore比ASP.NET更具优势的地方是什么&#xff1f; ASP.NET Core(ASP.NET Core 简介) ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET(ASP.NET 简介) ASP.NET 是一个成熟的框架&#xff0c;提…

Git学习笔记(七)——其他操作

一、自定义Git Git除了配置user.name 和user.email 还有很多可配置项。 &#xff08;1&#xff09;命令git config --global color.ui true 让Git显示颜色&#xff0c;会让命令输出看起来更醒目.Git 会适当显示不同的颜色。 $ git config --global color.ui true查看分支会有…

延迟队列docker插件

文章目录 目录 文章目录 前言 一、环境准备与使用 总结 前言 一、环境准备与使用 下载对应版本的插件 https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases 然后在虚拟机上随便找个文件夹 cd /home/docker 先把容器运行 然后在当前文件下 将插件拷贝…

半入耳式耳机运动会不会掉、佩戴超稳固的运动耳机推荐

现在越来越多的人开始意识到运动的重要性&#xff0c;用运动给身体增加一道“防护墙”是最好的生活方式了&#xff0c;不过&#xff0c;日复一日做着几乎相同的动作&#xff0c;难免索然无味&#xff0c;所以很多人都会选择在运动时戴上耳机听歌解闷&#xff0c;这时候也有不少…

选射频线缆的困难和调试多链路匹配板子的心酸

今日痛点&#xff1a;选电缆和板子出问题 选线缆选到后面&#xff1a;有同事给我说&#xff0c;我还要高精度转接头&#xff0c;我还要BNC转接头 ​ 你们如果知道我选择线缆的艰辛&#xff0c;换做你们会怎么想 附上我选择线缆的心得&#xff1a; 1.S11尽量要考虑桌子的宽度&a…

Vector - CAPL - 文件处理函数

在当前平台化的趋势下,就算是协议层测试依然需要适配各种各样的项目,也需要处理各类型的文件,那我们如何对文件进行读取、写入、修改等类型的操作呢?今天我们就会介绍此类型的函数,主要适用于text、bin文件的处理。 打开文件 Open

MySQL之Explain分析

4 Explain分析&#xff08;重点&#xff09; 4.1 Explain介绍 使用EXPLAIN关键字可以模拟优化器执行SQL语句&#xff0c;分析你的查询语句或是结构的性能瓶颈 在 select 语句之前增加 explain 关键字&#xff0c;MySQL 会在查询上设置一个标记&#xff0c;执行查询会返回执行…

TCP的11种状态

CLOSED状态&#xff1a;初始状态&#xff0c;表示TCP连接是“关闭的”或者“未打开的”LISTEN状态&#xff1a;表示服务端的某个端口正处于监听状态&#xff0c;正在等待客户端连接的到来SYN_SENT状态&#xff1a;当客户端发送SYN请求建立连接之后&#xff0c;客户端处于SYN_SE…

Gradle安装配置阿里云

Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置&#xff0c;也增加了基于Kotlin语言的kotlin-based DSL&#xff0c;抛弃了基于XML的各种繁琐配置。 面向Java应用为主。当前其支持的语言C、J…

数据结构——哈希表

一、哈希表介绍1.1 哈希表初了解哈希表是属于一个数据结构&#xff0c;并不是一个算法哈希表&#xff1a;hashtable&#xff0c;也叫散列表&#xff0c;根据关键码值(Key value)而直接进行访问的数据结构。通过把关键码值映射到表中的一个位置来访问记录&#xff0c;以加快查找…

解析永春堂1300模式为何风靡新零售市场

最近&#xff0c;永春堂1300模式风靡新零售市场&#xff0c;它凭借兼顾大、小、新、老会员&#xff0c;没有沉淀和泡沫等特点&#xff0c;引起市场的热切关注。而永春堂1300模式如此受欢迎的原因&#xff0c;最重要的&#xff0c;还是它丰厚的奖项报酬。永春堂1300直销模式主要…

Spring Boot 实现接口幂等性的 4 种方案

一、什么是幂等性 幂等是一个数学与计算机学概念&#xff0c;在数学中某一元运算为幂等时&#xff0c;其作用在任一元素两次后会和其作用一次的结果相同。 在计算机中编程中&#xff0c;一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数或幂…

多态与虚(函数)表

前言续接上回&#xff08;继承&#xff09;&#xff0c;我们了解了继承是如何通过虚基表&#xff0c;来解决派生类和父类有相同的成员变量的情况&#xff0c;但是类和对象中可不只有成员变量&#xff0c;如果成员函数也有同名&#xff0c;更或者如果我们想在访问不同情况&#…

Zeppelin【部署 01】Zeppelin最新版本zeppelin-0.10.1下载安装配置启动及问题处理(一篇学会部署Zeppelin)

1.简单介绍 来自百度百科&#xff1a; Apache Zeppelin 是一个让交互式数据分析变得可行的基于网页的开源框架。提供了数据分析、数据可视化等功能。是一个提供交互数据分析且基于Web的笔记本。方便你做出可数据驱动的、可交互且可协作的精美文档&#xff0c;并且支持多种语言…

JavaScript 简单计算

parseFloat和Number parseFloat()并不能进行数据类型转换&#xff0c; 所以对字符串进行parseFloat()是不起作用的&#xff0c; 需要使用Number()进行强制类型转换&#xff1b; 但是&#xff0c;如果真正涉及到精度计算&#xff0c;建议用decimal.js 毕竟&#xff0c;js的…