首页 > 编程语言 >一对一视频聊天源码,水印功能实现方案不容错过

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

时间:2024-05-18 09:52:36浏览次数:28  
标签:el canvas const waterMark 水印 water 容错过 源码 directives

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

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

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

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 url
  const 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 url
  const 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,
};

 

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

 

标签:el,canvas,const,waterMark,水印,water,容错过,源码,directives
From: https://www.cnblogs.com/yunbaomengnan/p/18199048

相关文章

  • Qt/C++音视频开发74-合并标签图形/生成yolo运算结果图形/文字和图形合并成一个/水印滤
    一、前言在使用yolo做人工智能运算后,运算结果除了一个方框,还可能需要增加文字显示在对应方框上,以便标记是何种物体,比如显示是人还是动物,或者还有可能追踪人员,显示该人员的姓名。这种应用场景非常普遍,而且非常有必要,可以非常直观的直接看到对应移动的物体是什么。当然也有个缺点,就......
  • 一对一视频源码,实现限流对优化系统性能尤为重要
    一对一视频源码,实现限流对优化系统性能尤为重要,主要内容为滑动日志,令牌桶,漏桶三种限流算法的Java实现获取连接许可的接口一、滑动日志用一个有序集合来存储所有请求的时间戳,以空间换时间的方式来简化计算二、令牌桶利用延迟计算来维护令牌数量三、漏桶漏桶算法原理类似......
  • 一对一视频聊天源码,JDBC数据源隔离方法
    在开发一对一视频聊天源码时,数据隔离需要对DB,Redis,RabbitMQ进行数据隔离,接下来主要介绍一下JDBC数据源隔离方法。通过实现Spring动态数据源AbstractRoutingDataSource,通过ThreadLocal识别出来压测数据,如果是压测数据就路由到影子库,如果是正常流量则路由到主库,通过流量识别的改......
  • 一对一视频源码,Redis数据源还能这样隔离
    一对一视频源码,Redis数据源还能这样隔离通过ThreadLocal识别出来压测数据,自定义Redis的主键的序列化方式,如果是压测数据则在主键后面加上后缀,这样就可以通过不同主键将Redis数据进行隔离。一、实现key序列化publicclassKeyStringRedisSerializerextendsStringRedis......
  • 暗水印——空域:二值化图像水印(看不见我吧 啦啦啦~)
    引言随着计算机和网络技术的飞速发展,信息的安全保护问题日益突出。数字图像、音频和视频等多媒体数字产品愈来愈需要一种有效的版权保护方法——水印技术,通常用于保护知识产权、防止未经授权的访问、作弊等。广义上可以把水印技术划分为四大类:图像水印、视频水印、音频......
  • kubernetes 源码开启 go work 模式
    为了更方便进行go项目多模块管理,go社区在gomod之后引入了go workspaces模式。kubernetes社区最近在 kubernetes源码中启用 go workspaces模式。go提出 go workspaces模式的issue,和社区 thockin 的关注 cmd/go:supportvendoringinworkspacemode·Is......
  • kubernetes DeploymentController 源码解析
    DeploymentController对象从 NewDeploymentController方法开始创建,我们首先看这个方法1//NewDeploymentControllercreatesanewDeploymentController.2funcNewDeploymentController(ctxcontext.Context,dInformerappsinformers.DeploymentInformer,rsInformer......
  • 基于Vue3水印组件封装:防篡改守护!
    基于Vue3的全新水印通用组件。这款组件不仅功能强大,而且易于集成,能够轻松为您的网页或应用添加自定义水印,有效防止内容被篡改或盗用。在线查看效果:原文可查看效果地址一,编写watermark组件<template><divref="watermarkContainerRef"class="watermark-container">......
  • DuckDB 源码配置
    DuckDB源码配置源码编译sudoapt-getupdate&&sudoapt-getinstall-ygitg++cmakeninja-buildlibssl-devgitclonegit@github.com:duckdb/duckdb.gitcdduckdbmakedebug/releaseissue当我们编译debug版本的时候可能会出现如下图所示的问题:调试配置{/......
  • 暗水印——空域:音频水印(看不见我吧 啦啦啦~)
    引言随着计算机和网络技术的飞速发展,信息的安全保护问题日益突出。数字图像、音频和视频等多媒体数字产品愈来愈需要一种有效的版权保护方法——水印技术,通常用于保护知识产权、防止未经授权的访问、作弊等。广义上可以把水印技术划分为四大类:图像水印、视频水印、音频水印和文......