本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
上篇文章说到了回溯系统用到的基础库rrweb以及其依赖的基础apiMutationObserver,下面我们就来了解一下rrweb包含的内容以及其实现。
组成
rrweb 主要由rrweb、rrweb-player、rrweb-snapshot三个库组成
- rrweb:提供了 record 和 replay 两个方法;record 方法用来记录页面上 DOM 的变化,replay 方法支持根据时间戳去还原 DOM 的变化。
- rrweb-player:基于 svelte 模板实现,为 rrweb 提供了回放的 GUI 工具,支持暂停、倍速播放、拖拽时间轴等功能。内部调用了 rrweb 的提供的 replay 等方法。
- rrweb-snapshot:包括 snapshot 和 rebuild 两大特性,snapshot 用来序列化 DOM 为增量快照,rebuild 负责将增量快照还原为 DOM。
工作流程
事件监听
record 方法内部会根据事件类型去初始化事件的监听,例如 DOM 元素变化、鼠标移动、鼠标交互、滚动等都有各自专属的事件监听方法。
详细的代码可以查看rrweb/src/recode/observer.ts
其中dom元素变化的监听用到我们之前提到浏览器apiMutationObserver
const observer = new (mutationObserverCtor as new (
callback: MutationCallback,
) => MutationObserver)(mutationBuffer.processMutations.bind(mutationBuffer));
DOM变化处理
节点分类
在监听到变化后回调MutationObserver提供的方法类processMutations,然后根据变化的类型进行不同的处理
详细的代码可以查看rrweb/src/record/mutation.ts
'characterData': 代表 characterData 节点变化,会记录在 this.texts 数组中,结构为 { node: Node, value: string },value 为 characterData 节点的最新值;
'attributes': 代表 DOM 属性变化,所有属性变化的节点会记录在 this.attributes 数组中,结构为 { node: Node, attributes: {} },attributes 中仅记录本次变化涉及到的属性;
'childList': 代表子节点树 childList 变化,包括节点新增,节点移动,节点删除。
1.节点新增
遍历addNodes调用genAdds方法,其中判断mirror中是否存在该节点,若不存在则为节点新增,将其添加到addedSet中,并且若该节点在droppedSet中,则将其从droppedSet中移除。
2.节点移动
遍历addNodes调用genAdds方法,其中判断mirror中是否存在该节点,若存在则为节点移动,将其添加到movedSet中
3.节点删除
遍历reomvedNodes,
1.若该节点是本次新增节点,则忽略该节点,并且从 addedSet 中移除该节点,同时记录到 droppedSet 中;(在处理新增节点的时候需要用到,虽然移除了该节点,但其子节点可能还存在于 addedSet 中,在处理 addedSet 节点时,会判断其祖先节点是否已被移除;) 2.需要删除的节点记录在 this.removes 中。
经过上面的分类处理后执行this.emit()方法
这里使用双向链表addList用来存储新节点,向其中插入节点的逻辑如下
- 若 DOM 节点的 previousSibling 已存在于链表中,则插入在
node.previousSibling节点后 - 若 DOM 节点的 nextSibling 已存在于链表中,则插入在
node.nextSibling节点前 - 都不在,则插入链表的头部
这样就可以保证兄弟节点的顺序。
以上就是rrweb对事件监听以及对DOM变化的处理,变化记录下来了,那引出的问题就是我们如何去还原?
这个就留到下次更新了~
相关材料:
写在最后
写作不易,点个赞好不好~
关于我(资深划水,顶级潜水员,不定期更新,写啥随意)