ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

基于rrweb框架对web 页面录制与回放

2021-05-23 16:05:39  阅读:380  来源: 互联网

标签:web 回放 DOM 录制 event rrweb events


官方文档:https://github.com/rrweb-io/rrweb/blob/0f86a72705b998a9abf5b4aae5b01a4f3b679954/guide.zh_CN.md

前言

最近来了个需求需要对web 页面上的操作行为做流程跟踪,并提供具体的数据,或者视频参照,作为合规的证据代码用户的操作轨迹,也避免一些纠纷。

对于web 页面的轨迹追踪,还需要知道操作行为。一想到这个第一想到的就是在线直播学习课程中,找个屏幕录播软件,讲解员打开该软件,开始在电脑上的一切行为都被录制成视频。然后就可以直接把视频分享给所有的人。而你再讲解过程中的一些列细节都能够播放出来。所以当初就想让原生APP开发一个这样类似的东西。再webview中进行播放。这样功能也就ok了。但是还是需要支持移动端web 网页的轨迹追踪。

解决问题方式

思路一:Canvas
利用Canvas截图,使用 html2canvas 库,不停的画页面然后不停的截图,再讲图片播放出来。后来调研了一下~ 发现这个可操作性几乎未零。太复杂了,数据量右大,而我们的页面又复杂,canvas 画半天耗内存,有时候会卡主。直接pass了

思路二:记录页面DOM变化
主要原理是:MutationObserver接口提供了监视对DOM树所做更改的能力。我们可以利用这个接口,保存每次变化的DOM数据,然后把这些数据转换成可视化的数据结构,然后给一个个保存起来。然后我们使用特定的方式对一个个保存起来的DOM数据进行还原并重新渲染出来。DOM节点的变化也就意味了页面轨迹发送了变化。这样就可以把这些轨迹记录下来。我们只需要再把这个还原出来的DOM播放的过程中进行录制,这样就可以保持下来这些轨迹视频。

进行实践
后续调研,以及其他同事的帮助,我们找到了LogRocket ,专业的web app用户行为记录工具,官网描述:

LogRocket记录用户在你的web上做的一切事情,以帮助你重现bug并更快的解决他们
在你的web app中发现问题不应该如此艰难
用LogRocket,重现问题就像他们发生在你自己的浏览器中一样
按照其描述,以及我们的进一步了解,发现LogRocket 里面的一些东西确实可以帮助我们解决问题。但是主要问题是还是因为LogRocket 这些数据,我们无法获取到自己的库里面。只能是第三方的库。并不满足我们的需求,还是就是要用它,需要花钱。
大家想深入的了解,可以查看官网 LogRocket 官网

后来我们又了解到一个开源的 rrweb 框架。rrweb主要由3部分组成:

rrweb-snapshot,包括快照和重建功能。快照用于将DOM及其状态转换为具有唯一标识符的可序列化数据结构; 重建功能是将快照重建为相应的DOM。
rrweb,包括两个功能:记录和重播。记录功能用于记录DOM中的所有动作变化; 重放是根据相应的时间戳逐个重放记录下的动作变化。
rweb-player是 rrweb的一个玩家用户界面,可以随时提供基于GUI的功能,如暂停,快进,拖放等功能。
使用这些我们可用于web界面录制以及web界面重放这两个主要功能,rrweb-snapshot 返回的数据结构是json格式的,方便我们前后台数据对接,也规范了这款业务逻辑操作。

rrweb 实践
引入库
(1) 直接通过script引入,推荐通过 jsdelivr 的 CDN 安装:

<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>

我们在被录制的应用中只需要引入录制部分代码即可,无需全量引入。

<script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/record/rrweb-record.min.js"></script>

(2) 通过 npm 引入rrweb 同时提供 commonJS 和 ES modules 两种格式的打包文件,易于和常见的打包工具配合使用。

npm install --save rrweb

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

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

// 通过 <script> 的方式,全量引入、npm 引入的
start() {
	let self = this
	this.operation = rrweb.record({
	   emit(event) {
	     	self.events.push(event);
	   },
	});
}
// 通过 <script> 的方式,仅仅引入录制部分代码
start() {
	let self = this
	this.operation = rrwebRecord({
	   emit(event) {
	     	self.events.push(event);
	   },
	   recordCanvas: true //支持录制canvas
	   //其他参数
	});
}

rrweb 在录制时会不断将各类 event 传递给配置的 emit 方法,你可以使用任何方式存储这些 event 以便之后回放。
当前rrweb 只支持录制静态的canvas,无法录制动态js生成的canvas.

录制结束
录制结束,rrwebRecord再次执行一遍,即可停止录制。

stop() { 
    this.operation && this.operation()
}

录制数据压缩
引入pako 来对录制的数据进行 gzip压缩,

npm install pako --save
zip(str) {
    let binaryString = pako.gzip(encodeURIComponent(str), { to: 'string' })
    return btoa(binaryString);
}

因为录制的是web页面,需要对数据进行encodeURIComponent 一遍,不然解压之后的数据会变成乱码

录制数据解压

unzip(b64Data) { 
    var strData   = atob(b64Data);
    var charData  = strData.split('').map(function(x){return x.charCodeAt(0);});
    var binData   = new Uint8Array(charData);
    var data    = pako.inflate(binData);
    // strData   = String.fromCharCode.apply(null, new Uint16Array(data));
    let array = new Uint16Array(data)
    var res = '';
    var chunk = 8 * 1024;
    var i;
    for (i = 0; i < array.length / chunk; i++) {
    res += String.fromCharCode.apply(null, array.slice(i * chunk, (i + 1) * chunk)); 
    }
    res += String.fromCharCode.apply(null, array.slice(i * chunk));

    strData = res   
    return decodeURIComponent(strData);
}

Uint16Array 对字符的长度有限制,所以采用分段解压的方式,这样就不会出现内存溢出的问题。

录制数据保存

save(events) {
    let self = this
    if (self.events.length === 0 || events&&events.length === 0) {
        return Promise.resolve();
    }
    const data = JSON.stringify(self.events)
    let logsParams = { "message": data, "setMessageTime": (new Date().getTime() + '')}
    
    const body = JSON.stringify({
        "__topic__": "topic",
        "__source__": "source",
        "__logs__": [
            logsParams
        ]
    });
    let url = 'xxx保存地址urlxxxxx'
    return new Promise((resolve, reject) => {
        fetch(url, {
            method: 'POST',
            headers: {
                'x-log-apiversion': '0.6.0',
                'x-log-bodyrawsize': sizeof(body),
                'Content-Type': 'application/json',
            },
            body,
        }).catch(error => {
            reject(error)
        }).then(response => {
            resolve(response)
        })
    })
}

一个更接近实际真实使用场景的示例如下:

// 每 10 秒调用一次 save 方法,避免请求过多

setInterval(save, 10 * 1000);

// 每调用一次,清空数据

this.events = []

当前我们使用的阿里云的日志接口,也可以改成其他的类似的请求接口。类似官网差不多的。当然我们也可以根据各自的需求,改善自己提交数据的策略。

录制数据播放
(1) 如果 rrweb 是全局引入的情况下,回放时需要引入对应的 CSS 文件:

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

再通过以下 JS 代码初始化 replayer:

const events = YOUR_EVENTS;
const replayer = new rrweb.Replayer(events);
replayer.play();

在实际的应用场景,我们需要异步请求数据,在对数据进行解压,然后再调用JS 。

fetch(url).catch(error => {
    reject(error)
}).then(res => {
    //
    const eventsDate = JSON.stringify(res.data)
    const events = unzip(eventsDate)
    const replayer = new rrweb.Replayer(events);
	replayer.play();
})

(2) 使用 rrweb-player
rrweb-player 同样可以使用 CDN 方式安装:

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

或者通过 npm 安装:

npm install --save rrweb-player

使用方式:

new rrwebPlayer({
  target: document.body, // 可以自定义 DOM 元素
  data: {
    events,
  },
  UNSAFE_replayCanvas: true //支持回放 canvas 
});

具体其他的api,或者其他的使用方式,大家可以查看官网

参考网址:

rrweb:打开 web 页面录制与回放的黑盒子
rrweb 官网
rrweb-snapshot 快照
rrweb-player 播放
转载:https://blog.csdn.net/yingyangxing/article/details/108267549
参考:https://blog.csdn.net/blackcat88/article/details/88972515

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input type="text" />
    <button onclick="start()">start</button>
    <button onclick="stop()">stop</button>
    <button onclick="Player()">Player</button>
    <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>

    <script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/record/rrweb-record.min.js"></script>
    
    <!-- 录制视频 播放 -->
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/rrweb-player@latest/dist/style.css"
    />
    <script src="https://cdn.jsdelivr.net/npm/rrweb-player@latest/dist/index.js"></script>

    <script>
      let events = [],
        operation;
      function start() {
        // rrweb行为录制
        operation = rrweb.record({
          emit(event) {
            // 用任意方式存储 event
            console.log(event);
            events.push(event);
          },
        });
      }

      function stop() {
        operation && operation();
      }

      function Player() {
        new rrwebPlayer({
          target: document.body, // 可以自定义 DOM 元素
          data: {
            events,
          },
        });
      }

      
    </script>
  </body>
</html>

标签:web,回放,DOM,录制,event,rrweb,events
来源: https://blog.csdn.net/qq_42374676/article/details/117196162

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有