网站制作公司如何运作,网站更改备案信息,php网站模板源码下载,深圳市建设交易网rrweb
背景
rrweb 是 record and replay the web#xff0c;是当下很流行的一个录制屏幕的开源库。与我们传统认知的录屏方式#xff08;如 WebRTC#xff09;不同的是#xff0c;rrweb 录制的不是真正的视频流#xff0c;而是一个记录页面 DOM 变化的 JSON 数组#x…rrweb
背景
rrweb 是 record and replay the web是当下很流行的一个录制屏幕的开源库。与我们传统认知的录屏方式如 WebRTC不同的是rrweb 录制的不是真正的视频流而是一个记录页面 DOM 变化的 JSON 数组因此不能录制整个显示器的屏幕只能录制浏览器的一个页签(录屏)。
rrweb事例展示流程图 意义解决问题 用户分析常规的指标数据只能做到一个统计。如果能通过录屏我们能完整分析某个客户的行为。 重现bug客户说有bug,但是复线不了环境不一样数据不一样。我们只能推断但是有了录屏我们就能很好的还原现场知道本质操作 代替视频录制 录制体积更⼩、清晰度⽆损的产品演⽰。纯粹是html不用装插件
基本使用
安装
npm install rrweb
录制
通过 rrweb.record 方法来录制页面emit 回调可接收到录制的数据。
import rrweb from rrweb;
// 1.录制
let events []; // 记录快照rrweb.record({emit(event) {// 将 event 存入 events 数组中events.push(event);},
});回放
通过 rrweb.Replayer 可回放视频需要传递录制好的数据。
// 2.回放
const replayer new rrweb.Replayer(events);
replayer.play();原理透析
基本概念 rrweb-snapshot 快照的生成
将页面中的dom转化为可序列化的数据结构并添加唯一标识
例如以下的 DOM 树
htmlbodyheader/header/body
/html会被序列化成类似这样的数据结构
{type: Document,childNodes: [{type: Element,tagName: html,attributes: {},childNodes: [{type: Element,tagName: head,attributes: {},childNodes: [],id: 3},{type: Element,tagName: body,attributes: {},childNodes: [{type: Text,textContent: \n ,id: 5},{type: Element,tagName: header,attributes: {},childNodes: [{type: Text,textContent: \n ,id: 7}],id: 6}],id: 4}],id: 2}],id: 1
}这个序列化的结果中有两点需要注意
我们遍历 DOM 树时是以 Node 为单位因此除了场景的元素类型节点以为还包括 Text Node、Comment Node 等所有 Node 的记录。我们给每一个 Node 都添加了唯一标识 id这是为之后的增量快照做准备。
在完成一次全量快照之后我们就需要基于当前视图状态观察所有可能对视图造成改动的事件在 rrweb 中我们已经观察了以下事件将不断增加增量序列化 DOM 变动 节点创建、销毁节点属性变化文本变化 鼠标移动 鼠标交互 mouse up、mouse downclick、double click、context menufocus、blurtouch start、touch move、touch end 页面或元素滚动 视窗大小改变 输入 类似git 先提交一个版本每次再追加追加。
记录的方法 MutationObserver
MutationObserver 是一个用于监听 DOM 变化的 JavaScript 接口。触发方式为批量异步回调一系列dom 变化之后通过其回调函数开始接收通知。MutationObserver 可以监听节点的添加、移除、属性变化等操作。
序列化中的特殊处理(只是对dom变化做了记录)
之所以说我们的序列化方法是非标准的是因为我们还需要做以下几部分的处理
去脚本化。被录制页面中的所有 JavaScript 都不应该被执行例如我们会在重建快照时将 script 标签改为 noscript 标签此时 script 内部的内容就不再重要录制时可以简单记录一个标记值而不需要将可能存在的大量脚本内容全部记录。记录没有反映在 HTML 中的视图状态。例如 input 输入后的值不会反映在其 HTML 中而是通过 value 属性记录我们在序列化时就需要读出该值并且以属性的形式回放成 。相对路径转换为绝对路径。回放时我们会将被录制的页面放置在一个iframe中此时的页面 URL为重放页面的地址如果被录制页面中有一些相对路径就会产生错误所以在录制时就要将相对路径进行转换同样的 CSS 样式表中的相对路径也需要转换。尽量记录 CSS 样式表的内容。如果被录制页面加载了一些同源的 样式表我们则可以获取到解析好的 CSS rules录制时将能获取到的样式都 inline 化这样可以让一些内网环境如 localhost的录制也有比较好的效果。
序列化
任何语言数据都是由数据结构来表示的我们将数据结构转换成二进制字符串的过程就是序列化
rebuild
将snapshot 记录的数据结构重建为对应的DOM
rrweb-player
为rrweb 提供的一套UI 控件提供基于GUI的
录制原理
MutationObserver
播放阶段
在序列化设计中我们提到了“去脚本化”的处理即在回放时我们不应该执行被录制页面中的 JavaScript在重建快照的过程中我们将所有 script 标签改写为 noscript 标签解决了部分问题。但仍有一些脚本化的行为是不包含在 script 标签中的例如 HTML 中的 inline script、表单提交等。
脚本化的行为多种多样如果仅过滤已知场景难免有所疏漏而一旦有脚本被执行就可能造成不可逆的非预期结果。因此我们通过 HTML 提供的 iframe 沙盒功能进行浏览器层面的限制(隔离环境,嵌入其他应用,兼容性考虑)。 rrweb 的播放器是在一个 iframe 上回放录屏的为了阻断 iframe 上的用户交互需要做一些特殊处理比如在 iframe 标签上设置 CSS 属性
pointer-events: none;为了去脚本化将 script 标签替换为 noscript 标签另外将 iframe 的 sandbox 属性设置为 “allow-same-origin”可以防止任何脚本的执行。
高精度计时器补全缺失节点模拟hover从任意时间开始播放
高精度计时器
之所以强调回放所⽤的计时器是⾼精度的是因为原⽣的 setTimeout 并不能保证在设置的延迟时间之后准确执⾏例如主线程阻塞时就会被推迟。
对于我们的回放功能⽽⾔这种不确定的推迟是不可接受的可能会导致各种怪异现象的发⽣因此我们通过 requestAnimationFrame根据设备的屏幕刷新率来调整动画的帧率 来实现⼀个不断校准的定时器确保绝⼤部分情况下操作的重放延迟不超过⼀帧。
同时⾃定义的计时器也是我们实现“快进”功能的基础。
补全缺失节点
在 rrweb 中当进行页面重放过程中如果发现了缺失的节点它会通过补全缺失节点的方式来还原页面的完整状态。
页面重放的过程是通过按照操作的记录序列逐步还原页面状态的。记录的操作序列包括了用户在页面上执行的各种操作比如点击、输入等。这些操作会导致页面上的节点发生相应的变化包括添加、删除、修改等。
然而在记录操作序列的过程中可能会发生节点变化的时机比较复杂的情况比如动态插入节点、使用 Shadow DOM、异步加载等。这些情况会导致在记录过程中无法捕获到节点变化的信息导致记录的操作序列中缺失了节点的变化信息。
为了解决这个问题rrweb 会使用 MutationObserver 监听页面上节点的变化。当发现有节点被添加或删除时rrweb 会将这些节点的信息记录下来并结合之前记录的操作序列进行分析。通过分析节点的变化情况以及操作序列中的操作类型和位置rrweb 可以推断出缺失的节点应该是什么并进行补全。
模拟 Hover
从任意时间点开始播放
除了基础的回放功能之外我们还希望 rrweb-player 这样的播放器可以提供和视频播放器类似的功能如拖拽到进度条至任意时间点播放。
实际实现时我们通过给定的起始时间点将快照链分为两部分分别是时间点之前和之后的部分。然后同步执行之前的快照链再正常异步执行之后的快照链就可以做到从任意时间点开始播放的效果。
播放器的进度条是如何控制与每个增量快照发生的时间对应上呢 比如在播放时用户点击进度条上的某一点这一点距离初始时间点是 timeOffset 长度点击的这个点可以叫做基线时间点 baselineTimerrweb 会根据这个点将所有的事件分成两部分前一部分是在基线时间点前已经发生的事件队列后一部分是待回放的事件队列。把前一部分事件同步还原构建完成作为后面队列的全量基准 DOM 树再继续异步地按照正确的时间间隔构建后面的增量快照。
问题扩展
如何将rrweb 转化为视频
rrvideo puppeteer 在服务端运行无头浏览器在无头浏览器中回放录制的数据然后每秒截取一定数量的图片最后通过 ffmpeg 合成视频。下面是大致的流程图