一对一视频聊天源码,水印功能实现方案不容错过

news/2024/11/18 7:33:11/文章来源:https://www.cnblogs.com/yunbaomengnan/p/18199048

一对一视频聊天源码,水印功能实现方案不容错过

一、基于原图生成水印图片(后端)

这种方案就是将 原图片 添加水印之后生成了 新图片,后续在一对一视频聊天源码前端页面进行展示是后端接口不返回原图片,而是返回带有水印的图片即可。

image.png

这种方式最大的优点就是安全,因为 水印图片 是后端生成的,前端只需要负责展示即可,不需考虑多余的问题,且即便在前端页面保存对应图片,拿到的仍然不是原图片。

二、基于 DOM 实现水印效果(前端)

自定义指令钩子非常多,但实际上能使用到的不多,比如最常用的就是 mounted、updated,在这我们只需要通过 mounted 即可实现对应的功能,并且核心代码比较简单。

核心内容

创建一个 watermark 的 DOM 节点,即 div 元素,用于包裹对应的 img 便于展示水印内容
在创建一个 waterbg 的 DOM 节点,即 div 元素

将 waterbg 节点作为 watermark 的 子节点,并进行 绝对定位 保证 waterbg 在 最上层显示
将对应的 水印标记 作为 waterbg 节点的 背景图片 展示
为 waterbg 节点设置 pointer-events: none; 实现 点击穿透
将 watermark 节点通过 insertBefore(…) 插入到 img 标签的前一个位置
再将 img 标签移动到 watermark 节点节中,这样就保证了新创建的 watermark 节点的位置一定是在原本 img 挂载的位置

效果和代码如下

image.png

// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import directives from './directives'createApp(App).use(directives).mount('#app');// src/directives/index.ts
import type { App } from 'vue'
import watermark from './waterMark'export default function installDirective(app: App) {app.directive(watermark.name, watermark.directives);
} // src/directives/waterMark.ts
import waterBgImg from '../assets/water-bg.png'const directives: any = {mounted(el: HTMLElement) {el.onload = () => {const { clientWidth, clientHeight, parentElement } = el;const waterMark: HTMLElement = document.createElement('div');const waterBg: HTMLElement = document.createElement('div');waterMark.className = `water-mark`;// 方便自定义展示结果// 创建 waterMark 父元素waterMark.setAttribute('style', `display: inline-block;overflow: hidden;position: relative;width: ${clientWidth}px; height: ${clientHeight}px;`);// 创建 waterBg 背景元素waterBg.className = `water-mark-bg`;// 方便自定义展示结果waterBg.setAttribute('style', `position: absolute;pointer-events: none;width: 100%;height: 100%;opacity: 0.2;background-image: url(${waterBgImg}); background-repeat: repeat;`);// 为 waterMark 添加对应的子元素
            waterMark.appendChild(waterBg);// 将 waterMark 插入到对应的位置parentElement?.insertBefore(waterMark, el);// 将图片元素移动到 waterMark 中
            waterMark.appendChild(el);}}
}export default {name: 'watermark',directives
}

 

优化实现方式

在上述的实现方式中,实际上至少有两点可优化的点:

所有的样式直接以 字符串 形式出现在 JavaScript 代码中

可以将一对一视频聊天源码中对应的静态样式部分处理在 css 中,由 .water-mark 来管理
多余的 waterBg 被创建出来

其实完全没必要单独创建这个节点元素,可以直接基于 .water-mark 对应节点的伪类元素来实现
优化后核心代码如下:

/********* src/directives/waterMark.ts ***********/const directives: any = {mounted(el: HTMLElement) {el.onload = () => {const { clientWidth, clientHeight, parentElement } = el;const waterMark: HTMLElement = document.createElement("div");// 创建 waterMark 父元素waterMark.setAttribute("style", `width: ${clientWidth}px; height: ${clientHeight}px;`);waterMark.className = `water-mark`; // 方便自定义展示结果// 将 waterMark 插入到对应的位置parentElement?.insertBefore(waterMark, el);// 将图片元素移动到 waterMark 中
      waterMark.appendChild(el);};},
};export default {name: "watermark",directives,
};/********* css 部分代码如下  ***********/ 
.water-mark {display: inline-block;overflow: hidden;position: relative;
}
.water-mark::after {pointer-events: none;position: absolute;content: ' ';width: 100%;height: 100%;opacity: 0.2;background-image: url("../assets/water-bg.png");background-repeat: repeat;
}

 

三、基于 Canvas 实现水印效果(前端)

基于 Canvas 实现方式的优点就在于能够动态的设置水印内容,相比于上一种基于固定背景图片的方式更灵活,具体效果如下:

0652BD27.gif

核心步骤

通过 canvas 填充文本,并通过 canvas.toDataURL(“image/png”); 获取到对应的 base64 格式的图片
将这个 base64 格式的图片作为类名为 water-mark 节点的背景图

利用 background-repeat: repeat; 让这个图重复填充背景即可
为 water-mark 节点设置 pointer-events: none; 实现 点击穿透
利用对应图片的父元素作为 water-mark 节点的相对定位节点,保证绝对定位的 water-mark 节点显式在对应图片之上

效果和代码如下

0652BD27.gif

/********* src/directives/waterMark.ts  ***********/ // 全局保存 canvas 和 div ,避免重复创建(单例模式)
const globalCanvas = null;
const globalWaterMark = null;// 获取 toDataURL 的结果
const getDataUrl = ({font = "16px normal",fillStyle = "rgba(180, 180, 180, 0.3)",textAlign,textBaseline,text = "请勿外传",
}) => {const rotate = -20;const canvas = globalCanvas || document.createElement("canvas");const ctx = canvas.getContext("2d"); // 获取画布上下文
ctx.rotate((rotate * Math.PI) / 180);ctx.font = font;ctx.fillStyle = fillStyle;ctx.textAlign = textAlign || "left";ctx.textBaseline = textBaseline || "middle";ctx.fillText(text, canvas.width / 3, canvas.height / 2);return canvas.toDataURL("image/png");
};// 设置水印
const setWaterMark = (el: HTMLElement, binding: any) => {const { parentElement } = el;// 获取对应的 canvas 画布相关的 base64 urlconst url = getDataUrl(binding);// 创建 waterMark 父元素const waterMark = globalWaterMark || document.createElement("div");waterMark.className = `water-mark`; // 方便自定义展示结果waterMark.setAttribute("style", `background-image: url(${url});`);// 将对应图片的父容器作为定位元素parentElement.setAttribute("style", "position: relative;");// 将图片元素移动到 waterMark 中
  parentElement.appendChild(waterMark);
};const directives: any = {mounted(el: HTMLElement, binding: any) {el.onload = setWaterMark.bind(null, el, binding.value);},
};export default {name: "watermark",directives,
};/*********  css 部分  ***********/ 
.water-mark {display: inline-block;overflow: hidden;position: absolute;left: 0;top: 0;width: 100%;height: 100%;pointer-events: none;background-repeat: repeat;
}

 

使用 MutationObserver 优化

以上提到的两种前端实现方案,都存在一个问题很明显的问题,那就是用于只要用户通过 开发者调试工具 来稍微操作一,就能够导致水印失效:

删除对应 dom 节点
设置对应 dom 节点的 css 样式
0652BD27.gif

MutationObserver 接口提供对 DOM 树监听的能力,它能够监听 DOM 树属性、节点本身、子节点等的变化,于是优化的思路就是使用 MutationObserver 去监听外部对应 water-mark 节点的操作,只要监听到了就重新渲染水印效果即可。

效果和代码

【注意】这里最容易踩坑的点就是 MutationObserver 中的条件写得不正确的话会导致死循环.
0652BD27.gif

/********* src/directives/waterMark.ts  ***********/ // 全局保存 canvas 和 div ,避免重复创建(单例模式)
const globalCanvas = null;
const globalWaterMark = null;// watermark 样式
let style = `
display: block;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-repeat: repeat;
pointer-events: none;`;const getDataUrl = ({font = "16px normal",fillStyle = "rgba(180, 180, 180, 0.3)",textAlign,textBaseline,text = "请勿外传",
}) => {const rotate = -20;const canvas = globalCanvas || document.createElement("canvas");const ctx = canvas.getContext("2d"); // 获取画布上下文
ctx.rotate((rotate * Math.PI) / 180);ctx.font = font;ctx.fillStyle = fillStyle;ctx.textAlign = textAlign || "left";ctx.textBaseline = textBaseline || "middle";ctx.fillText(text, canvas.width / 3, canvas.height / 2);return canvas.toDataURL("image/png");
};const setWaterMark = (el: HTMLElement, binding: any = {}) => {const { parentElement } = el;// 获取对应的 canvas 画布相关的 base64 urlconst url = getDataUrl(binding);// 创建 waterMark 父元素const waterMark = globalWaterMark || document.createElement("div");waterMark.className = `water-mark`; // 方便自定义展示结果style = `${style}background-image: url(${url});`;waterMark.setAttribute("style", style);// 将对应图片的父容器作为定位元素parentElement.setAttribute("style", "position: relative;");// 将图片元素移动到 waterMark 中
  parentElement.appendChild(waterMark);
};// 监听 DOM 变化
const createObserver = (el: HTMLElement, binding: any) => {const waterMarkEl = el.parentElement.querySelector(".water-mark");const observer = new MutationObserver((mutationsList) => {if (mutationsList.length) {const { removedNodes, type, target } = mutationsList[0];const currStyle = waterMarkEl.getAttribute("style");// 证明被删除了if (removedNodes[0] === waterMarkEl) {observer.disconnect();init(el, binding);} else if (type === "attributes" &&target === waterMarkEl &&currStyle !== style) {waterMarkEl.setAttribute("style", style);}}});observer.observe(el.parentElement, {childList: true,attributes: true,subtree: true,});
};// 初始化
const init = (el: HTMLElement, binding: any = {}) => {// 设置水印
  setWaterMark(el, binding.value);// 启动监控
  createObserver(el, binding.value);
};// 定义指令配置项
const directives: any = {mounted(el: HTMLElement, binding: any) {el.onload = init.bind(null, el, binding);},
};export default {name: "watermark",directives,
};

 

以上就是一对一视频聊天源码,水印功能实现方案不容错过, 更多内容欢迎关注之后的文章

 

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何从多个文件夹内转移全部文件(忽略文件夹的结构)(进行复制)(再打包)

首先,需要用到的这个工具:度娘网盘 提取码:qwu2 蓝奏云 提取码:2r1z 04文件夹里面有只有1个名称为"1"的文件夹,“1”里面有“2”,“2”有“3”,“3”有“4”,从“1”开始,都有5个兔兔的图片,这是“1”里面的文件夹结构,现在要做的就是忽略文件夹结构,提…

安装vue/cli报错问题解决

在管理员终端中输入命令:npm i -g @vue/cli错误原因证书已过期,需要安装淘宝镜像npm config set registry https://registry.npmmirror.com使用cnpm安装脚手架报错cnpm i -g @vue/cli 这个错误表明你尝试执行的 cnpm 命令无法加载,因为 PowerShell 策略不允许执行该脚本文件…

如何定时关闭程序

首先,需要用到的这个工具:度娘网盘 提取码:qwu2 蓝奏云 提取码:2r1z 前几步的流程参考之前发过的文章: 《快捷自由定时重启、注销、关机》只不过最后的地方,选择 关闭程序 ,再填写程序名称即可补充:如何找出程序名称常规方法:从C、D、E等盘里面去找从任务管理器找:1…

一对一视频app开发,RabbitMQ数据隔离步骤详解

一对一视频app开发,RabbitMQ数据隔离详解一、自动创建影子队列因为SpringAMQP中的中的关键方法是私有的,无法通过继承的方式进行实现对以配置好的队列进行扩展,所以需要自定义该类,来实现对自动创建影子队列,并和交换器进行绑定代码实现改造RabbitListenerAnnotationBeanP…

3、Oracle 中的过滤、排序

最近项目要用到Oracle,奈何之前没有使用过,所以在B站上面找了一个学习视频,用于记录学习过程以及自己的思考。 视频链接: 【尚硅谷】Oracle数据库全套教程,oracle从安装到实战应用 如果有侵权,请联系删除,谢谢。更加详细的教程,可以直接观看此链接Oracle教程 1、过滤 1…

一对一视频聊天源码,JDBC数据源隔离方法

在开发一对一视频聊天源码时,数据隔离需要对DB,Redis,RabbitMQ进行数据隔离,接下来主要介绍一下JDBC数据源隔离方法。通过实现Spring动态数据源AbstractRoutingDataSource,通过ThreadLocal识别出来压测数据,如果是压测数据就路由到影子库,如果是正常流量则路由到主库,通过…

解锁服务器连接状态新姿势:tcping工具助你高效诊断网络连通性

使用 tcping 工具检测服务器连接状态使用 tcping 工具检测服务器连接状态 在IT运维环境中,由于安全考虑,很多服务器和交换机可能会禁用ICMP(Internet Control Message Protocol)响应,即“ping”请求,以防止ICMP FLOOD攻击和不必要的资源消耗。然而,运维人员仍需要一种方…

lazarus交叉编译

lazarus交叉编译lazarus交叉编译 虽然lazarus可以安装在windows,linux,macos。。操作系统上面,但是交叉编译有其独到的方便之处,比如,可以在windows上用lazarus编译所有OS平台的程序。 1)下载并编译fpcupdeluxe 下载地址:https://github.com/LongDirtyAnimAlf/fpcupdeluxe…

读人工智能时代与人类未来笔记06_机器学习的力量

读人工智能时代与人类未来笔记06_机器学习的力量1. 挑战 1.1. 不同的目标和功能需要不同的训练技术 1.2. 我们必须关注人工智能的潜在风险 1.2.1. 应对日益盛行的人工智能将带来的风险,是一项必须与该领域的进步并行的任务 1.3. 我们…

[转帖]JVM内存配置最佳实践

https://help.aliyun.com/zh/sae/use-cases/best-practices-for-jvm-heap-size-configuration 如果JVM堆空间大小设置过大,可能会导致Linux系统的OOM Killer被激活,进而结束(kill)Java应用进程,在容器环境下可能会表现为频繁异常重启。本文介绍在容器环境下JVM堆参数的配…

高通在推动混合 AI 规模化 扩展方面独具优势

高通在推动混合 AI 规模化 扩展方面独具优势 摘要 正如白皮书第一部分所言,在云端和终端进行分布式处理的混合 AI 才是 AI 的未来。混合 AI 架 构,或仅在终端侧运行 AI,能够在全球范围带来成本、能耗、性能、隐私、安全和个性化优势。 高通正在助力实现随时随地的智能计算。…

自动获取随笔链接(用于博客签名或者版权信息)

今天弄了好久也不知道怎样自动获取每篇文章对应的链接,就在这时,突然发现博客签名里面有一个模板选项,下拉有个 转载声明 选择框,如下:点完之后,出现了我想要的东西!!这不就是我心心念念、苦苦寻找的自动获取文章链接函数吗?! <p>本文来自博客园,作者:{autho…