重新认识下网页水印
使用背景图图片

单独使用 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);// 根据容器宽高动态生成divfor (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 = fontctx.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 = 100const waterWidth = 100const { 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) {// 水印画布透明度为0if (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
相关文章:
重新认识下网页水印
使用背景图图片 单独使用 css 实现,使用 backgroundImage,backgroundRepeat 将背景图片平铺到需要加水印的容器中即可。 如果希望实现旋转效果,可以借助伪元素,将背景样式放到伪元素中,旋转伪元素实现: &l…...
Unity脚本练习
在C# 中 class 是创建类的标志,要创建类的话得现有class上面这个的逻辑是 类的访问权限, 关键字,类名以及类继承的父类在Unity中创建一个脚本或者添加一个组件,就相当于在Unity命名空间中创建了一个可以访问的类。这些类能够直接在…...
二十分钟带你了解JVM性能调优与实战进阶
ZGC 诞生原因 Java生态非常强大,但还不够,有些场景仍处于劣势,而ZGC的出现可以让Java语言抢占其他语言的某些特定领域市场。比如 谷歌主导的Android手机系统显示卡顿。证券交易市场,实时性要求非常高,目前主要是C主…...
对比应用层和内核层区别
一、所使用的空间不同: 应用层使用的空间是0-3G的用户空间。 内核层使用的是3-4G的内核空间。 二、打印信息所用函数不同: 应用层使用printf打印信息。 printf("打印信息\n"); 内核层使用printk打印信息。 …...
Hadoop服役新服务器
目录 0、准备一台新服务器 1、修改主机名 2、配置静态ip 3、配置xshell登录 4、关闭并禁用防火墙 5、分发hadoop和jdk文件 6、分发环境变量文件 7、source 环境变量 8、配置ssh 9、删除105节点的data、logs文件夹 10、单节点启动并关联到集群 11、验证新节点是否有效 0…...
YOLOv8详解 【网络结构+代码+实操】
文章目录YOLOv8 概述模型结构Loss 计算训练数据增强训练策略模型推理过程网络模型解析卷积神经单元(model.py)Yolov8实操快速入门环境配置数据集准备模型的训练/验证/预测/导出使用CLI使用python多任务支持检测实例分割分类配置设置操作类型训练预测验证…...
Visual Studio Code 1.76 发布
欢迎使用 Visual Studio Code 2023 年 2 月版,其中一些亮点包括: 配置文件 - 活动配置文件徽章,通过命令面板快速切换配置文件。辅助功能改进 - 新的音频提示,改进的终端屏幕阅读器模式。可移动的 Explorer 视图- 将资源管理器放…...
Vulnhub靶场----3、DC-3.2
文章目录一、环境搭建二、渗透流程三、思路总结一、环境搭建 靶场下载地址:https://download.vulnhub.com/dc/DC-3-2.zip kali:192.168.144.148 DC-3.2:192.168.144.151 更改驱动器连接设置: 二、渗透流程 1、信息收集nmap -T5 -…...
Windows电脑密码忘记解决方法
目录 背景 方法一 方法二 方法三 方法四 方法五 背景 个人电脑忘记了密码,无法登录用户界面。 方法一 1. 开机时常按 F11,如果是Win10一下系统,就常按 F8,知道出现一下图状 2. 选择疑难解答,再选择高级选项 3.…...
ChatGPT相关技术必读论文100篇(2.27日起,几乎每天更新)
按上篇文章《ChatGPT技术原理解析:从RL之PPO算法、RLHF到GPT-N、instructGPT》的最后所述 为了写本ChatGPT笔记,过去两个月翻了大量中英文资料/paper(中间一度花了大量时间去深入RL),大部分时间读的更多是中文资料 2月最后几天读的更多是英文…...
【算法】算法题解---电话号码的字符组合
算法名称 电话号码的字符组合 算法描述 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 示例 1: 输入&…...
提高上限之数学学习——数制转换及MECE原则学习
文章目录数制转换不同数制表达数制转换的方法换基法(换向十进制)除余法(十进制向其他进制转换)按位拆分法和按位合并法判断一个整数a,是否是2的整数次幂MECE原则学习数制转换 不同数制表达 数制转换的方法 换基法(换向十进制) 定义:给出数制转换的定量…...
字符函数和字符串函数(下)——“C”
各位CSDN的uu们你们好呀,今天小雅兰的内容依旧是字符函数和字符串函数呀,这篇博客会讲一些内存相关的函数,下面,让我们进入字符函数和字符串函数的世界吧 字符串查找 strstr strtok 错误信息报告 strerror 字符操作 内存操作函…...
kafka docker 安装
先启动起 zookeeper (1)服务: 192.168.190.35docker run -d --name kafka1 \-p 9092:9092 \-e KAFKA_BROKER_ID0 \-e delete.topic.enabletrue \-e num.partitions1 \-e KAFKA_ZOOKEEPER_CONNECT192.168.192.35:2181 \-e KAFKA_ADVERTISED_LI…...
SpringBean管理
一.什么是SpringBean? 在Spring中将管理对象称为 Bean.Bean是由一个SpringIOC容器实例化,组装和管理的对象.也就是说,Bean并不是由我们程序员编写的,而是在程序运行过程中,由Spring通过反射机制生成的. SpringBean是Spring框架在运行时管理的对象,我们编写的大多数逻辑代码都…...
关于Vue3中reactive的意义
在学习Vue3的时候产生疑问: const addForm reactive({ // 这里面的reactive啥意思sysPre: null,diaPre: null,tem: null })查询解决 在Vue3中,响应式对象是指通过reactive函数转换而来的对象,它的属性可以被Vue自动监测,当属性…...
平衡三进制
平衡三进制 一、定义 平衡三进制,也称为对称三进制。这是一个不太标准的 计数体系。 正规的三进制的数字都是由 0,1,2 构成的,而平衡三进制的数字是由 -1,0,1 构成的。它的基数也是 3(因为有三个可能的值)。由于将 -1 写成数字…...
python爬取网站数据
开学前接了一个任务,内容是从网上爬取特定属性的数据。正好之前学了python,练练手。 编码问题 因为涉及到中文,所以必然地涉及到了编码的问题,这一次借这个机会算是彻底搞清楚了。 Unicode是一种编码方案,又称万国码…...
CSS的三大特性
🌟所属专栏:前端只因变凤凰之路🐔作者简介:rchjr——五带信管菜只因一枚😮前言:该系列将持续更新前端的相关学习笔记,欢迎和我一样的小白订阅,一起学习共同进步~👉文章简…...
Linux-scheduler之负载均衡(二)
四、调度域 SDTL结构 linux内核使用SDTL结构体来组织CPU的层次关系 struct sched_domain_topology_level {sched_domain_mask_f mask; //函数指针,用于指定某个SDTL的cpumask位图sched_domain_flags_f sd_flags; //函数指针,用于指定某个SD…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
Linux部署私有文件管理系统MinIO
最近需要用到一个文件管理服务,但是又不想花钱,所以就想着自己搭建一个,刚好我们用的一个开源框架已经集成了MinIO,所以就选了这个 我这边对文件服务性能要求不是太高,单机版就可以 安装非常简单,几个命令就…...
