前言

作为web开发工程师,为了保障页面的质量,咱们会去做单测,E2E测试,一起也会有专门的测试工程师介入,来帮咱们扫清页面中的bug,可是你永久不能确保自己的代码在用户电脑上百分之百靠谱,所以在前端工程化方面,咱们有各式各样的埋点计划,全链路监控、用户行为上报、接口状况监控等等。

看起来是很齐全了,但这些都是针对用户行为的瞬间采样,尽管看起来很完备,可是当实践遇到问题时,许多时分还需求开发者自己去思考上下文。

作为产品经理,我的产品推行出去了,尽管埋点记载能捕获大部分的用户行为,可是有时分我仍是想调查用户的真实运用链路。在不涉及隐私的前提下,想要对用户行为进行深度了解,进一步对用户行为进行定性分析。

作为测试同学,看到线上出了一部分的bug,我是很想穿越回用户身边,想看看用户到底是经过什么样的操作手段触发了这个问题,很有或许这是一个未曾考虑到的测试用例。

以上种种问题,都跟咱们今日聊的用户行为录制有很大的联系,有了这个玩意,咱们能够具体的回归用户的一切操作,既能够复原用户操作排查问题,也能够协助产品上调查用户运用行为,测试也能够依据这个录制构成自动化回归用例。

用户行为录制是一个定性分析东西,记载用户整个会话的每一个点击、滑动、输入等行为,并支撑以录像的办法来回放这些操作,既然要录制用户的完好操作流程,那么咱们从前端视角来看看,别离都有哪些技能计划能够协助咱们完结这一功用。

计划:快照截图

首要咱们能够想到是否能运用Canvas截图,不断的画页面然后不断的截图,做过一些营销活动的同学必定对这个计划不会生疏,它是用户记载和共享页面信息的有效手段。

那么这个计划能否满意咱们的需求呢?

依据图片是否由设备本地生成,快照可分为前端处理和后端处理两种办法,因为后端生成的计划依赖于网络通讯,一起需求发动无头浏览器,不行避免地存在通讯开销和等待时延,一起关于模板和数据结构改动也有必定的保护本钱,咱们这儿暂时只考虑前端生成计划。

原理

前端侧关于快照的处理进程,实质上是将DOM节点包括的视图信息转化为图片信息的进程。这个进程能够借助Canvas的原生API完结,这也是计划可行性的根底。

具体来说,转化进程是将方针DOM节点制造到canvas画布,然后canvas画布以图片办法导出。可简略标记为制造阶段和导出阶段两个步骤:

  • 制造阶段:挑选期望制造的DOM节点,依据nodeType调用canvas目标的对应API,将方针DOM节点制造到canvas画布(例如关于用户行为录制技术方案的制造运用drawImage办法)。
  • 导出阶段:经过canvas的toDataURL或getImageData等对外接口,终究完结画布内容的导出。

以上计划说起来比较简略,可是实践开发需求处理的内容比较多,比方:

  • canvas的drawImage办法只承受CanvasImageSource,而CanvasImageSource并不包括文本节点、普通的div等,将非用户行为录制技术方案的元素制造到canvas需求特定处理。
  • 当 DOM杂乱时,层级优先级处理较为杂乱。
  • 需求关注浮动,绝对定位等布局定位的处理。
  • 许多新的 css 特点无法处理。

这儿咱们挑选了社区中比较知名的计划:HTML2Canvas,官方地址为:github.com/niklasvh/ht…

这儿咱们主要考虑4个点:

  • 页面动画是否能制造?no
  • 页面款式是否不错位?no
  • 交互性的行为是否能记载?
  • 跨域的图片资源能否记载,播映的视频能否记载?

Canvasdemo

用户行为录制技能计划

Canvas总结

从上面demo中咱们能够看到html2canvas并不是一个完美的录制计划,从技能上来讲

  • 布局会有误差,input 框中款式产生了错位
  • 部分CSS款式不支撑:html2canvas.hertzen.com/features
  • 无法制造动画,demo 中 css 动画的状况无法制造
  • 假如有 video 的话,视频播映状况等无法记载

另外就算上面计划的技能问题能够处理,这种计划的弊端也非常显着:功用太差了,无论是制造的功用仍是传输的功用,所以这个计划不太契合咱们的需求。

计划:视频录制

既然截图作用差,功用也欠好,那么咱们能够直接录制咱们当前的页面,将视频流存储起来,那么现在有相应的才能来满意咱们的需求吗,哎,好像webRTC能够啊。

首要简略了解一下webRTC:浏览器上的音视频通讯相关的才能叫做WebRTC(realtimecommunication),是跟着网速越来越快,音视频需求越来越多,而被浏览器所完结的音视频的标准API。

音视频通讯的流程有五步:搜集、编码、通讯、解码、烘托。

其不只能够用于「音视频录制、视频通话」,还能够用在「照相机、音乐播映器、同享远程桌面、即时通讯东西、P2P网络加速、文件传输、实时人脸辨认」等场景上。

但咱们来看下它的兼容性情况:

用户行为录制技能计划

webRTCdemo

webRTC总结

经过上面的demo,咱们发现webrtc作用上是能满意咱们的需求,可是其有一个最大的弊端:那便是因为安全性策略,在发动webRTC之前,会有弹窗问询用户,需求取得用户的赞同,一起在敞开之后会有一个录制的状况条,所以在整个的进程关于用户而言会有一些侵入性,用户会有显着的感知。

一起webrtc需求部署在https环境下,这也是一个额定的要求。

明显这个技能点更适合用在一些清晰的监控场景下,比方面试、上课等,所以不太契合咱们的需求。

计划:dom序列化

接下来便是咱们今日的主角了,也是现在最流行的处理计划,

从作用上讲,它能够比美webrtc的录制,基本上咱们的操作都能被正确录制下来,一起因为它存储了完好的网页信息,所以能够将页面布局做到很好地复原。

从功用上讲,因为它是dom序列化今后的结构,一起是做的增量存储,所以它的传输量比webrtc或者是截图的办法都要低一些。

原理

网页本质上是一个DOM节点办法存在,经过浏览器烘托出来。咱们是否能够把DOM以某种办法保存起来,并且在不一起间节点持续记载DOM改动状况,再将数据复原成DOM节点烘托出来完结回放呢?

从技能规划上,大致都能够拆分为DOM序列化、构建快照序列、回放重演以及沙箱环境等四个方向。

元素序列化

需求保存某一时间的页面状况时,最直观的便是将那一刻的页面dom结构做个快照,然后在浏览器中从头烘托出来就能到达回溯的作用了。

constcloneDoc=document.documentElement.cloneNode(true);//录制
document.replaceChild(cloneDoc,document.documentElement);//回放

这样咱们就完结了某一时间DOM快照的功用。可是这个录制的cloneDoc还仅仅内存中的一个目标,并没有完结远程存储,所以咱们需求有一种办法将页面中的一切元素序列化成一个普通目标,这样就能像保存一个字符串或者是json文本相同将相关数据传到后台服务器中。

为了完结这个功用,咱们需求将cloneDoc这个目标序列化成字符串,保存到服务端,然后在回放的时分从服务器上取出来,交给浏览器从头烘托。

//XMLSerializer是浏览器自带的api,能够将dom目标序列化成string
constserializer=newXMLSerializer();
conststr=serializer.serializeToString(cloneDoc);
document.documentElement.innerHTML=str;

可是咱们的意图是录制视频,只要一个dom快照明显是不行的。了解动画的同学都应该知道,动画是由每秒至少24帧的画面按次序播映而产生的,那简略,咱们就每秒钟clone24次呗。

略微细想便可知道这种办法底子行不通,原因有以下几点:

  • 每秒clone24次整个页面内容,对功用损耗巨大,严重影响用户体验
  • 每秒要将24帧的页面内容上传到服务端,对网络开销也是巨大的
  • 回放时,每秒要烘托24个完好的html内容,浏览器底子做不到这么快
  • 还有,要是页面没改动,那么24帧的数据或许是完全相同的,底子没必要clone这个屡次。

综上问题,咱们面临着2个比较大的应战,

  • 一个是页面dom结构无法序列化,所谓不行序列化是指尽管咱们能够经过innerHTML等⽅式获取到描述DOM的⽂本格局,但其间会丢掉⼀些视图状况,例如元素的value就不⼀定会记载在HTML中。
  • 页面记载的办法有问题,存在很多的冗余记载,一起传输耗费大

依据以上缺点,咱们需求规划一套可序列化的页面结构,一起只要比及页面有改动的时分,做增量记载,即只记载改动的部分。这样一来,好处就显而易见了

  • 页面结构的状况能够保存下来
  • 只记载改动的部分,比起记载整个网页要小的多。这样对网页的功用、网络的开销都会小许多。
  • 咱们只在页面有改动的时分才记载,这样一来,很多重复数据的问题也给处理了。
  • 回放时,咱们只需求首要将榜首帧(完好的页面内容)先烘托出来,然后在依照记载的时间,按次序将改动的部分烘托到页面。这样就能够像看视频相同来回溯用户的操作流程了。

现在市面上较为知名的DOM结构描述库有parse5,咱们能够参阅一下。

在线演示地址:astexplorer.net/#/1CHlCXc4n…

ps:咱们后面介绍的rrweb并未选用这个解析库,而是自己完结了一套,具体的原因咱们待会再说。

监听改动

在上一步中,咱们已经从理论上完结了录制和回放的功用,可是具体完结呢?咱们怎么才能知道页面什么时分改动呢?

或许有的同学就想到了咱们的主角:MutationObserver。

该接口供给了监督对DOM树所做更改的才能,当DOM发生改动时,经过批量异步的办法去触发回调,并将DOM改动经过MutationRecord数组传给回调办法,咱们能够运用这个接口,保存每次改动的DOM数据,并把这些数据转化成可视化的数据结构,然后别离保存起来。接着运用特定的办法对之前保存起来的DOM数据进行复原并从头烘托出来。DOM节点的改动也就意味了页面轨道发生了改动,这样就能够把这些轨道记载下来。

可是它没法盯梢像input、textarea、select这类可交互元素的输入。

关于这种可交互的元素,咱们主要靠监听input和change来记载输入的进程。

可是有些元素的值是经进程序直接设置的,这样是不会触发input和change事件的。这种情况下咱们能够经过劫持对应特点的setter来到达监听的意图。

记载增量数据有许多种办法,比方domdiff,最开端rrweb团队也尝试过这个计划,可是发现在dom杂乱的场景下页面开销太高了,所以抛弃了计划。

终究他们选用的快照+OPlog的办法,他们把引发视图改动的操作归为以下⼏类:

  • DOM改动

    • 节点创立、毁掉
    • 节点特点改动
    • ⽂本改动
  • ⿏标交互

  • ⻚⾯或元素翻滚

  • 视窗⼤⼩改动

  • 输⼊

  • ⿏标移动(特指⿏标的视觉方位)

关于每个操作只需求记载其操作类型和相关的数据,就能够在回放时重现对应的操作,也就回放了该操作对视图的改动。

这样只需求在开端录制时制造⼀个完好的DOM快照,之后则记载一切的操作数据,这些操作数据咱们称之为Oplog(operationslog),这⼀思路和log-structuredfilesystem是相似的。

用户行为录制技能计划

回放重演

简略来说,重演便是将搜集到的数据依照次序顺次“播映”一遍,视频文件的播映需求音视频解码器,而咱们的重演环节要做的工作就能够简略了解成一个Web运用解码器,从用户端搜集上来的数据结构除了要做清洗和存储外,还不能直接被回放侧运用,其间有不少需求考虑的细节。

咱们举几个具体的比方

  • 页面中的JS脚本要不要履行?假如履行,那不是一切的接口交互意味着都会在发生一次,那关于用户数据是有破坏性的,假如不履行?那么这些操作假如制止?

    • 改写script为noScript,一起经过沙箱阻挠JS的行为
  • 怎么校准回放时的守时器误差?

    • 运用requestAnimationFrame代替settimeout,一起不断地做纠正
  • 怎么过滤用户隐私?

    • 约好部分类名的结构不做具体value存储

沙箱

上面咱们说到了回放的一些问题点,其间js履行是最大的一个应战,而沙箱就能够协助咱们处理该问题,它是为了给回放供给一个安全可控的运转环境,既然选用DOM快照计划,那么便需求考虑怎么制止一些“不安全”的DOM操作。例如运用内链接跳转、咱们不太或许会直接给用户打开一个新的tab,为了确保快照状况顺次回放,咱们还需求考虑怎么安全精确的反序列化构建DOM。

rrweb在重建快照时将被录制的DOM重建在⼀个iframe元素中,经过设置它的sandbox特点,咱们能够禁⽌以下⾏为:

  • 表单提交
  • window.open等弹出窗
  • JS脚本(包括inlinescript,eventhandler和url操作)

rrweb

上面咱们说到过许屡次rrweb了,现在让咱们好好来介绍下它,其官方地址是:github.com/rrweb-io/rr…

这是一个开源的Web会话回放库,供给了易于运用的API来记载用户的交互并远程回放。rrweb主要由rrweb、rrweb-player和rrweb-snapshot三个库组成:

  • rrweb:供给了record和replay两个办法;record办法用来记载页面上DOM的改动,replay办法支撑依据时间戳去复原DOM的改动。
  • rrweb-player:依据svelte模板完结,为rrweb供给了回放的GUI东西,支撑暂停、倍速播映、拖拽时间轴等功用。内部调用了rrweb供给的replay等办法。
  • rrweb-snapshot:包括snapshot和rebuilding两大特性,snapshot用来序列化DOM为增量快照,rebuilding担任将增量快照复原为DOM。

上面咱们有说到rrweb的dom序列化计划是自己完结的,这点其官方文章也给出了解释:

咱们不使⽤⼀些开源⽅案例如parse5的原因包括两个⽅⾯:

  1. 咱们需求完结⼀个“⾮标准”的序列化⽅法。
  2. 此部分代码需求运⾏在被录制的⻚⾯中,要尽或许的控制代码量,只保存必要功用。

之所以说咱们的序列化⽅法是⾮标准的是因为咱们还需求做以下⼏部分的处理:

  1. 去脚本化,被录制⻚⾯中的一切JavaScript都不应该被执⾏。
  2. 记载没有反映在HTML中的视图状况。例如输⼊后的值不会反映在其HTML中,咱们需求读取其value值并加以记载。
  3. 相对路径转化为绝对路径。回放时⻚⾯URL为重放⻚⾯的地址,假如被录制⻚⾯中有⼀些相对路径就会产⽣过错。
  4. 尽量记载CSS款式表的内容。假如被录制⻚⾯加载了⼀些同源的款式表,咱们则能够获取到解析好的CSSrules,录制时将能获取到的款式都inline化,这样能够让⼀些内⽹环境(如localhost)的录制也有⽐较好的回放作用。

rrwebdemo

沙箱回放仍是录制视频存储?

在咱们的录制数据中,有许多的外链资源,比方cdn的文件,外链的图片等等,也便是说在咱们运用录制的数据进行回放的时分,需求依赖这张图片。可是跟着项意图迭代,这张图片很或许早已不在,这时咱们在回放时,页面中的图片就会加载不出来。比方一个稳妥场景,保额信息就在网站内的一张海报上,客户或许会说:“我其时看到的保额明明是150万,怎么现在变成100万了?”,这时你要怎么证明其时海报上写的便是100万保额呢?

所以最稳妥的计划仍是将rrweb录制的原始数据转化成视频,这样一来,不管网站怎么改动,迭代了多少版本,视频是不受影响的。我的做法是经过puppeteer在服务端运转无头浏览器,在无头浏览器中回放录制的数据,然后每秒截取必定数量的图片,最终经过ffmpeg合成视频。

当然这儿rrweb官方也有供给一些处理计划,比方后端能够在每次数据上传今后将外链资源扫描出来,然后存在本地服务器端,这姿态能够确保拥有对资源的控制权。这儿就依照各自的具体场景决议了。

总结

以上便是咱们今日一切的共享内容了,咱们从最简略的守时截图到webrtc录制,再到最终的dom序列化录制计划,一步步地讲解了用户录制的技能计划选型和应战,期望能协助咱们对这一技能点有所启示和协助。

参阅资料

  • rrweb:打开 web 页面录制与回放的黑盒子
  • rrweb 浏览器录制及转视频计划

团队介绍

以上便是本次共享的全部内容,来自团队 @少和 共享,期望对你有所协助^_^

喜爱的话别忘了共享、点赞、收藏

咱们是来自大应科技(Aloudata,www.aloudata.com)的前端团队,担任大应科技全线产品前端开发工作。在一家初创公司,前端团队不只要快速完结事务迭代,还要一起建设前端工程化的落地和新技能的预研,咱们会环绕产品品质、开发功率、创意与前沿技能等多方向对大前端进行探究和共享,包括但不限于功用监控、组件库、前端结构、可视化、3D 技能、在线编辑器等等。