当前位置: 首页 > news >正文

重新认识下网页水印

使用背景图图片

在这里插入图片描述

单独使用 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

http://www.lryc.cn/news/39604.html

相关文章:

  • Unity脚本练习
  • 二十分钟带你了解JVM性能调优与实战进阶
  • 对比应用层和内核层区别
  • Hadoop服役新服务器
  • YOLOv8详解 【网络结构+代码+实操】
  • Visual Studio Code 1.76 发布
  • Vulnhub靶场----3、DC-3.2
  • Windows电脑密码忘记解决方法
  • ChatGPT相关技术必读论文100篇(2.27日起,几乎每天更新)
  • 【算法】算法题解---电话号码的字符组合
  • 提高上限之数学学习——数制转换及MECE原则学习
  • 字符函数和字符串函数(下)——“C”
  • kafka docker 安装
  • SpringBean管理
  • 关于Vue3中reactive的意义
  • 平衡三进制
  • python爬取网站数据
  • CSS的三大特性
  • Linux-scheduler之负载均衡(二)
  • VScode第三方插件打开sqlite数据库
  • Kafka 监控
  • MultipartFile与File的互转
  • 数据结构与算法基础-学习-15-二叉树
  • 接口测试要测试什么?
  • 2023.03.12学习总结
  • 数据结构入门6-1(图)
  • 把C#代码上传到NuGet,大佬竟是我自己!!!
  • 解决前端“\n”不换行问题
  • Python打包成exe,文件太大问题解决办法(比保姆级还保姆级)
  • CSS弹性布局flex属性整理