前端录制回放rrweb

rrweb 是 ‘record and replay the web’ 的简写,旨在利用现代浏览器所提供的强大 API 录制并回放任意 web 界面中的用户操作。

rrweb中文文档 https://github.com/rrweb-io/rrweb/blob/master/guide.zh_CN.md
本文项目地址 https://github.com/qdfudimo/vue-rrweb 大家点个星

demo介绍

该项目分为客户端服务端

  1. 客户端 vue3 cd ./rrweb-client 执行 pnpm dev
  2. 服务端使用 node 通过上传的json数据保存在本地文件中,在调用接口的时候再读取文件内容返回给后台 cd ./rrweb-serve 执行 pnpm dev

在这里插入图片描述

rrweb 介绍

rrweb 主要由 3 部分组成:

  • rrweb-snapshot,包含 snapshot 和 rebuild 两个功能。snapshot 用于将 DOM 及其状态转化为可序列化的数据结构并添加唯一标识;rebuild 则是将 snapshot 记录的数据结构重建为对应的 DOM。
  • rrweb,包含 record 和 replay 两个功能。record 用于记录 DOM 中的所有变更(mutation);replay 则是将记录的变更按照对应的时间一一重放。
  • rrweb-player,为 rrweb 提供一套 UI 控件,提供基于 GUI 的暂停、快进、拖拽至任意时间点播放等功能。

使用指南

直接通过 <script> 引入

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.css"
/>
<script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.js"></script>

通过 npm 引入

npm install --save rrweb rrweb-player

rrweb 同时提供 commonJS 和 ES modules 两种格式的打包文件,易于和常见的打包工具配合使用。

兼容性

由于使用 MutationObserver API,rrweb 不支持 IE11 以下的浏览器。可以从这里找到兼容的浏览器列表。

隐私

页面中可能存在一些隐私相关的内容不希望被录制,rrweb 为此做了以下支持:

  • 在 HTML 元素中添加类名 .rr-block 将会避免该元素及其子元素被录制,回放时取而代之的是一个同等宽高的占位元素。
  • 在 HTML 元素中添加类名 .rr-ignore 将会避免录制该元素的输入事件。
  • 所有带有.rr-mask类名的元素及其子元素的 text 内容将会被屏蔽。
  • input[type="password"] 类型的密码输入框默认不会录制输入事件。
  • 配置中还有更为丰富的隐私保护选项。

快速开始

<template>
  <div id="replay" ref="replay" v-if="isPlaying" />
  <textarea placeholder-class="textarea-placeholder" />
  <button type="button" @click="handelStart">开始录制</button>
  <button type="button" @click="handelPasue">暂停</button>
  <button type="button" @click="handelRecord">回放</button>
  <button type="button" @click="handelReRecord">网络回放</button>
  <button type="button" @click="handelRequest">请求</button>
  <button type="button" @click="handelPayStart">支付开始</button>
  <button type="button" @click="handelPayEnd">支付结束</button>
  <div class="modal rr-mask" v-if="isRecording">正在录制</div>
</template>

录制

如果通过 <script> 的方式仅引入录制部分,那么可以访问到全局变量 rrwebRecord,它和全量引入时的 rrweb.record 使用方式完全一致,以下示例代码将使用后者。

import * as rrweb from 'rrweb';
import rrwebPlayer from 'rrweb-player';
import 'rrweb-player/dist/style.css';
import { ref, onUnmounted } from 'vue';
import axios from "axios";
const events = ref([])
const replay = ref()
/**是否正在回放 */
const isPlaying = ref(false)
/**是否正在录制 */
const isRecording = ref(false)
let stopFn = null
let replayInstance = null;
const handelStart = () => {
  isPlaying.value = false;
  isRecording.value = true;
  events.value = []
  stopFn = rrweb.record({
    emit(event) {
      // 用任意方式存储 event
      events.value.push(event)
      // 以 rrwebEvents 的长度作为分片持续上传 防止数据过大
      if (events.value.length >= 100) {
        //超过100 上传给后台 同时重置为空
        uploadFile()
        events.value = []
      }
    },
  });
}

rrweb 在录制时会不断将各类 event 传递给配置的 emit 方法,你可以使用任何方式存储这些 event 以便之后回放。

调用 record 方法将返回一个函数,调用该函数可以终止录制:

停止录制

/**let stopFn = rrweb.record({
  emit(event) {
    if (events.length > 100) {
      // 当事件数量大于 100 时停止录制
      stopFn();
    }
  },
}); */
/**暂停录屏且上传 */
const handelPasue = () => {
  isRecording.value = false
  stopFn()
  if (events.value.length === 0) return
  uploadFile();
  events.value = []
}

上传数据

/**
 *  压缩 events 数据,并上传至后端
 *用于将 events 发送至后端存入,并重置 events 数组
 */
const uploadFile = () => {
  console.log("上传快照了");
  axios('/apis/uploadFile', {
    method: 'post',
    headers: {
      'Content-type': 'application/json'
    },
    data: JSON.stringify({
      events: events.value
    })
  })
    .then(response => {
      console.log('response', response)
    })
    .catch(error => {
      console.log('error', error)
    })
}

回放

npm install --save rrweb-player
import rrwebPlayer from 'rrweb-player';
import 'rrweb-player/dist/style.css';
使用

通过 props 传入 events 数据及配置项

/**new rrwebPlayer({
  target: document.body, // 可以自定义 DOM 元素
  // 配置项
  props: {
    events,
  },
}); */
const handelRecord = () => {
  if (isRecording.value) {
    console.log("请先暂停录制");
    return
  }
  isPlaying.value = true
  //vue异步更新 为了获取到replay dom
  setTimeout(() => {
    replayInstance = new rrwebPlayer({
      target: replay.value, // 可以自定义 DOM 元素
      // 配置项
      props: {
        events: events.value,
        skipInactive:false,	//是否快速跳过无用户操作的阶段
        showDebug: false, //是否在回放过程中打印 debug 信息
        showWarning: false, //是否在回放过程中打印警告信息
        autoPlay: true, //是否自动播放
        showControlle :true,//是否显示播放器控制 UI
        speedOption:[1, 2, 4, 8] //倍速播放可选值
      },
    });
    replayInstance.addEventListener("finish", (payload) => {
      console.log(payload,2222);
    })
  }, 100);
}

请求后台数据

/**网络请求回放 */
const handelReRecord = () => {
  axios('/apis/getFile', {
    method: 'post'
  })
    .then(res => {
      if (res.data.code == 200) {
        let { data = [] } = res.data
        if (data.length) {
          events.value = data;
          // replayInstance.destroy()
          // replayInstance = null
          handelRecord()
        }
      }
    })
    .catch(error => {
      console.log('error', error)
    })
}

vite配置跨域

 server: {
    proxy: { // 跨域代理
      '/apis': {
        // target: 'http://' + env.VUE_APP_BASE_API,
        target: 'http://localhost:3000/', // 
        changeOrigin: true,
        logLevel: 'debug',
        rewrite: (path) => path.replace(/^\/apis/, '')
      },
    },
  }

node服务端

pnpm i express body-parser
const express = require("express")
const bodyParser = require('body-parser')
const fs = require('fs')
const path = require('path')
const app = express()
app.use(bodyParser.urlencoded({
    extended: false
}))
app.use(bodyParser.json())
app.post("/uploadFile",(req,res)=>{
    console.log(req.body,11);
    const jsonFile = path.join(process.cwd(), `./file/jsonFile${Date.now()}.json`)
    fs.writeFileSync(jsonFile, JSON.stringify(req.body.events))
    res.send({
        data:"",
        msg:"上传成功",
        code:200
    })
})
app.post("/getFile",(req,res)=>{
    const fileDirPath = path.join(process.cwd(), `./file`);
    const files = fs.readdirSync(fileDirPath);
    console.log(files);
    let file;
    if(files && files.length) {
        file = fs.readFileSync(`${fileDirPath}/${files[files.length-1]}`); // 此处只取第一个文件片段验证
    }
    res.send({
        data:JSON.parse(file),
        msg:"上传成功",
        code:200
    })
})
// 清理文件内容
app.post('/clearFile', ctx => {
    const fileDirPath = path.join(process.cwd(), `./file`);
    const files = fs.readdirSync(fileDirPath);
    if(files && files.length) {
        files.forEach(item => {
            const filePath = `${fileDirPath}/${item}`;
            fs.unlinkSync(filePath);
        })
    }
    ctx.response.body = {
        status: '00'
    }
})
app.get("/count",(req,res)=>{
    res.send("1111")
})
// 2. 设置请求对应的处理函数
//    当客户端以 GET 方法请求 / 的时候就会调用第二个参数:请求处理函数
app.get('/', (req, res) => {
  res.send('hello world')
})
 
// 3. 监听端口号,启动 Web 服务
app.listen(3000, () => console.log('app listening on port 3000!'))

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

目标检测算法:YOLO v1论文解读

目标检测算法&#xff1a;YOLO v1论文解读 前言 ​ 其实网上已经有很多很好的解读各种论文的文章了&#xff0c;但是我决定自己也写一写&#xff0c;当然&#xff0c;我的主要目的就是帮助自己梳理、深入理解论文&#xff0c;因为写文章&#xff0c;你必须把你所写的东西表达清…

深入了解Java虚拟机之高效并发

目录 Java内存模型与线程 概述 硬件的效率与一致性 Java内存模型 主内存与工作内存 内存间交互操作 对于volatile型变量的特殊规则 原子性、可见性与有序性 先行发生原则 Java与线程 线程实现 线程调度 状态切换 小结 线程安全与锁优化 概述 线程安全 Java中…

简单的UDP网络程序·续写

该文承接文章 简单的UDP网络程序 对于客户端和服务端的基本源码参考上文&#xff0c;该文对服务器润色一下&#xff0c;并且实现几个基本的业务服务逻辑 目录 demo1 第一个功能&#xff1a;字典翻译 初始化字典 测试代码&#xff1a;打印 字符串分割 客户端修改 成品效果…

AI宝典:AI超强工具大整合

&#x1f604;&#x1f604;个人介绍 光子郎.进行开发工作七年以上&#xff0c;目前涉及全栈领域并进行开发。会经常跟小伙伴分享前沿技术知识&#xff0c;java后台、web前端、移动端&#xff08;Android&#xff0c;uniapp&#xff0c;小程序&#xff09;相关的知识以及经验体…

C#发送邮箱设置及源码

用C#调用发送邮箱代码之前需要邮箱开通SMTP/POP3及设置授权码&#xff0c;开通及获取方法如下&#xff1a; 1、打开邮箱&#xff0c;登录邮箱&#xff0c;进入设置&#xff0d;》帐户 2、在“帐户”设置中&#xff0c;找到服务设置项&#xff0c;进行设置&#xff0c;如下…

[Flink] Flink On Yarn(yarn-session.sh)启动错误

在Flink上启动 yarn-session.sh时出现 The number of requested virtual cores for application master 1 exceeds the maximum number of virtual cores 0 available in the Yarn Cluster.错误。 版本说明&#xff1a; Hadoop&#xff1a; 3.3.4 Flink&#xff1a;1.17.1 问题…

Python实战基础20-解密文件及目录操作

任务1 为泸州驰援湖北的89名白衣勇士点赞 【任务描述】 设计python程序&#xff0c;实现用户可以为泸州驰援湖北的89名白衣勇士点赞留言。用户点赞留言内容保存到本地txt文件中。 import os # 导入os模块 import random # 导入随机模块 import string # 导入string模块# 定义…

《Lua程序设计》--学习3

输入输出 简单I/O模型 Lua 文件 I/O | 菜鸟教程 (runoob.com) 暂留 补充知识 局部变量和代码块 Lua语言中的变量在默认情况下是全局变量&#xff0c;所有的局部变量在使用前必须声明 在交互模式中&#xff0c;每一行代码就是一个代码段&#xff08;除非不是一条完整的命…

chatgpt赋能python:Python如何将IP地址转换为整数

Python如何将IP地址转换为整数 在计算机网络中&#xff0c;IP地址是一个包含32位的二进制数字&#xff0c;通常由四个8位二进制数字&#xff08;即“点分十进制”&#xff09;表示。但在某些情况下&#xff0c;需要将IP地址转换为整数&#xff0c;例如在网络编程中检查网络连接…

Ingress详解

Ingress Service对集群外暴露端口两种方式&#xff0c;这两种方式都有一定的缺点&#xff1a; NodePort &#xff1a;会占用集群集群端口&#xff0c;当集群服务变多时&#xff0c;缺点明显LoadBalancer&#xff1a;每个Service都需要一个LB&#xff0c;并且需要k8s之外设备支…

FPGA量子类比机制-FPQA,将在量子运算设计中引发一场新的革命

1980年代现场可程式化逻辑门阵列(FPGA)的出现彻底改变了电子设计。大约40年后&#xff0c;现场可程式化量子位元阵列(FPQA)可望在量子运算电路设计中引发一场类似的革命。 1980年代现场可程式化逻辑闸阵列(FPGA)的出现彻底改变了电子设计。FPGA允许设计人员创建适合特定应用的…

ArrayList 万字长文解析:使用、优化、源码分析

文章目录 ArrayList 万字长文解析&#xff1a;使用、优化、源码分析前言ArrayList 简介ArrayList 的基本使用方法ArrayList 性能优化ArrayList 的源码分析内部结构构造方法解析扩容机制System.arraycop与 Arrays.copyof 实现方式 与 使用场景迭代器 JDK 8版本 ArrayList bug 示…

【基于Rsync实现Linux To Windows文件同步】

基于Rsync实现Linux To Windows文件同步 简介安装步骤安装Linux服务器端1.安装rsync2.启动Rsync3.验证是否启动成功4.修改rsyncd.conf重启rsync服务 安装Windows客户端1.rsync客户端安装&#xff1a;2.配置环境变量3.测试rsync命令4.创建密码文件5.密码文件授权6.查看服务端需要…

Python高光谱遥感数据处理与机器学习实践技术丨Matlab高光谱遥感数据处理与混合像元分解

目录 Python高光谱遥感数据处理与机器学习实践技术 第一章 高光谱基础 第二章 高光谱开发基础&#xff08;Python&#xff09; 第三章 高光谱机器学习技术&#xff08;python&#xff09; 第四章 典型案例操作实践 Matlab 高光谱遥感数据处理与混合像元分解 第一章 理论…

【大数据之路4】分布式计算模型 MapReduce

4. 分布式计算模型 MapReduce 1. MapReduce 概述1. 概念2. 程序演示1. 计算 WordCount2. 计算圆周率 π 3. 核心架构组件4. 编程流程与规范1. 编程流程2. 编程规范3. 程序主要配置参数4. 相关问题1. 为什么不能在 Mapper 中进行 “聚合”&#xff08;加法&#xff09;&#xff…

操作系统原理 —— 什么是基本分页存储管理?(二十二)

在操作系统中&#xff0c;一个新的进程需要载入内存当中执行&#xff0c;在装入的时候需要给该进程分配一定的运行内存&#xff0c;在之前的章节中讲解了连续分配的几种方式&#xff0c;比如&#xff1a;单一连续分配、固定分区分配、动态分区分配&#xff0c;还讲解了对应的动…

Nacos架构与原理 - 总体架构

文章目录 Nacos 起源Nacos 定位Nacos 优势Nacos 生态Nacos 总体设计设计原则架构图用户层业务层内核层插件 小结 Nacos 起源 Nacos 在阿里巴巴起源于 2008 年五彩石项目&#xff08;完成微服务拆分和业务中台建设&#xff09;&#xff0c;成长于十年双十⼀的洪峰考验&#xff…

基于遗传算法的柔性生产调度研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

软件测试金融测试岗面试热点问题

1、网上银行转账是怎么测的&#xff0c;设计一下测试用例。 回答思路&#xff1a; 宏观上可以从质量模型&#xff08;万能公式&#xff09;来考虑&#xff0c;重点需要测试转账的功能、性能与安全性。设计测试用例可以使用场景法为主&#xff0c;先列出转账的基本流和备选流。…

DHT11温湿度传感器

接口定义 传感器通信 DHT11采用简化的单总线通信。单总线仅有一根数据线&#xff08;SDA&#xff09;&#xff0c;通信所进行的数据交换、挂在单总线上的所有设备之间进行信号交换与传递均在一条通讯线上实现。 单总线上必须有一个上拉电阻&#xff08;Rp&#xff09;以实现单…