目前,在我们的项目中通常会使用各种各样的埋点和监控来收集页面访问的信息,例如点击埋点、PV埋点等,这些埋点数据能够反应绝大部分的用户行为,但是对于一些关注上下文的使用场景而言这些埋点是不够的。
因此,我们需要一种手段来获取用户某一时段连续的操作行为,也就是录制用户行为,包括整个会话中的每一个点击、滑动、输入等行为,同时支持回放录制的操作行为,完整且真实地重现用户行为以帮助我们回溯或分析某些使用场景。
录制用户行为最容易想到的就是将屏幕操作通过视频的方式录制下来,目前浏览器本身已经提供了一套基于音视轨的实时数据流传输方案 WebRTC(Web Real-Time Communications),在我们的录屏使用场景主要关注以下几个 API:
整体录制流程如下:
mediaDevices.getDisplayMedia()
由用户授权选择屏幕进行录制,获取到数据流;new MediaRecorder()
对象录制获取的屏幕的数据流;ondataavailable
监听事件用于获取录制的 Blob 数据。<template>
<video ref="playerRef"></video>
<button @click="handleStart">开启录制</button>
<button @click="handlePause">暂停录制</button>
<button @click="handleResume">继续录制</button>
<button @click="handleStop">结束录制</button>
<button @click="handleReplay">播放录制</button>
<button @click="handleReset">重置内容</button>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
const playerRef = ref();
const state = reactive({
mediaRecorder: null as null | MediaRecorder,
blobs: [] as Blob[],
});
// 开始录制
const handleStart = async () => {
const stream = await navigator.mediaDevices.getDisplayMedia();
state.mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/webm',
});
state.mediaRecorder.addEventListener('dataavailable', (e: BlobEvent) => {
state.blobs.push(e.data);
});
state.mediaRecorder?.start();
};
// canvas录制(特殊处理)
const handleCanvasRecord = () => {
const stream = canvas.captureStream(60); // 60 FPS recording
const recorder = new MediaRecorder(stream, {
mimeType: 'video/webm;codecs=vp9',
});
recorder.ondataavailable = (e) => {
state.blobs.push(e.data);
};
}
// 暂停录制
const handlePause = () => { state.mediaRecorder?.pause() };
// 继续录制
const handleResume = () => { state.mediaRecorder?.resume() };
// 停止录制
const handleStop = () => { state.mediaRecorder?.stop() };
// 播放录制
const handleReplay = () => {
if (state.blobs.length === 0 || !playerRef.value) return;
const blob = new Blob(state.blobs, { type: 'video/webm' });
playerRef.value.src = URL.createObjectURL(blob);
playerRef.value.play();
};
const handleReset = () => {
state.blobs = [];
state.mediaRecorder = null;
playerRef.value.src = null;
};
const handleDownload = () => {
if (state.blobs.length === 0) return;
const blob = new Blob(state.blobs, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.style.display = 'none';
a.download = 'record.webm';
a.click();
};
</script>
尽管浏览器原生提供了这样既简单又实用的屏幕录制解决方案,但在我们实际应用场景中仍旧有非常多的问题:
众所周知,视频是由一帧帧的画面组合而成的,因此我们可以按照一定时间间隔来截图的方式保存当前页面快照,然后将快照按照相同的截取速度播放形成视频就能实现用户行为录制了。最常用的截图方法就是以 html2canvas 库为代表的 canvas 截图,我们在使用过程中也发现了较多问题:
<template>
<el-button @click="handleStart">开启录制</el-button>
<el-button @click="handleStop">停止录制</el-button>
<el-button @click="handleReplay">播放录制</el-button>
<img :src="state.imgs[state.num ?? 0]" />
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import html2canvas from 'html2canvas';
const state = reactive({
visible: false,
imgs: [] as string[],
num: 0,
recordInterval: null as any,
replayInterval: null as any,
});
const FPS = 30;
const interval = 1000 / FPS;
const handleStart = async () => {
handleReset();
state.recordInterval = setInterval(() => {
if (state.imgs.length > 100) {
handleStop();
return;
}
html2canvas(document.body).then((canvas: any) => {
const img = canvas.toDataURL();
state.imgs.push(img);
});
}, interval);
};
const handleStop = () => {
state.recordInterval && clearInterval(state.recordInterval);
};
const handleReplay = async () => {
state.recordInterval && clearInterval(state.recordInterval);
state.num = 0;
state.visible = true;
state.replayInterval = setInterval(() => {
if (state.num >= state.imgs.length - 1) {
clearInterval(state.replayInterval);
return;
}
state.num++;
}, interval);
};
const handleReset = () => {
state.imgs = [];
state.recordInterval = null;
state.replayInterval = null;
state.num = 0;
};
</script>
实际内容截图效果!
每一个瞬间我们看到的页面都是浏览器当前渲染的 DOM节点,那么我们完全可以将 DOM 节点保存下来,并持续记录 DOM 节点的变化,然后再将记录的 DOM 节点数据通过浏览器渲染回放,这样即可实现用户行为录制的需求。
整个思路非常简单,但具体实现起来是非常复杂的事情,我们需要考虑 DOM 节点数据如何保存、如何捕获用户行为并记录 DOM 节点变换和如何将记录的数据在浏览器上回放出来等。
所幸当前社区已经有非常成熟的库,也就是 rrweb(record and replay the web)
rrweb 主要由 3 部分组成:
录制过程
rrweb 在录制时会首先进行首屏 DOM 快照,遍历整个页面的 DOM Tree 并通过 nodeType 映射转换为 JSON 结构数据。针对不同 nodeType 类型的节点类型的序列化操作具有非常多的细节,如想了解细节可阅读这部分源码。全量快照数据示例如下:
在获取首屏全量快照之后,我们就需要监听各类变动以获取增量的数据,增量改变的数据也需要同步转换为 JSON 数据进行存储。对于增量数据更新,则是通过 mutationObserver 获取 DOM 增量变化,以及通过全局事件监听、事件(属性)代理的方式进行方法(属性)劫持,并将劫持到的增量变化数据存入 JSON 数据中。针对不同类型的变动有这不同的监听处理方式,如想了解细节可阅读这部分源码。
回放过程
回放主要就是将录制过程中的全量快照和增量快照进行重建复原,那么为保证一个安全可靠的环境在回放时我们不应该执行被录制页面中的 JavaScript,在重建快照的过程中通过 script 标签改写为 noscript 标签和 dom 重建在 iframe 中并设置 sandbox 属性等手段来构建安全可靠的沙盒环境。在沙盒环境中,首先需要对首屏 DOM 快照进行重建,遍历 JSON 产物的同时通过自定义 type 映射到不同的节点构建方法以重建首屏 DOM 结构,然后 rrweb 内部则根据不同的增量类型调用不同的函数在页面对增量数据进行展现。同时,播放时通过录制产生的时间戳来保证回放顺序,通过 Node id 作用至指定的 DOM 节点,通过 requestAnimationFrame 保证页面播放流畅度。
rrweb 实现原理可参考以下相关资料以及源码/官方文档:
- rrweb:打开 web 页面录制与回放的黑盒子 - 知乎
- 神策数据王磊:如何用 JS 实现页面录制与回放 - 掘金
- rrweb录屏原理浅析 - 掘金
- rrweb 实现原理介绍 - 微信公众号
- rrweb 带你还原问题现场 - 云音乐大前端
<template>
<button @click="handleStart">开启录制</button>
<button @click="handleStop">结束录制</button>
<button @click="handleReplay">播放录制</button>
<div class="replay" ref="replayRef"></div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import * as rrweb from 'rrweb';
import rrwebPlayer from 'rrweb-player';
import 'rrweb-player/dist/style.css';
const replayRef = ref();
const state = reactive({
events: [] as any[],
stopFn: null as any,
});
const handleStart = () => {
state.stopFn = rrweb.record({
emit(event) {
if (state.events.length > 100) {
// 当事件数量大于 100 时停止录制
handleStop();
} else {
state.events.push(event);
}
},
});
ElMessage('开始录制');
};
const handleStop = () => {
state.stopFn?.();
ElMessage('已停止录制');
};
const handleReplay = () => {
new rrwebPlayer({
target: replayRef.value, // 可以自定义 DOM 元素
// 配置项
props: {
events: state.events,
},
});
};
</script>
从效果上来讲,rrweb 录制内容存储了完整的页面结构能够较好地还原页面的整个操作,并且 rrweb 录制无损录制具有较好的清晰度,不像视频录制和页面截图需要考虑分辨率与产物大小的问题,同时也不像 canvas 截图一样对内容和样式有较大的局限性使得部分页面内容无法录制。
从性能上来讲,rrweb 录制传输的内容为 JSON 数据并且只对用户操作内容作增量记录,当页面静默的时候不会有额外数据的记录,相比较视频录制和页面截图而言大大减少了最终产物的体积,减轻了数据传输的压力,同时也提高了录制的性能。
从功能上来讲,rrweb 除了基础的录制回放功能之外,还具有较好的可扩展性和可操作性:
对比内容 | 视频录制 | 页面截图 | Dom 快照录制 |
---|---|---|---|
开源库 | WebRTC 原生支持 | html2canvas | rrweb |
用户感知 | 录制有感 | 录制无感 | 录制无感 |
产物大小 | 大 | 大 | 相对较小 |
兼容性 | 详见相关 API 兼容性 | 部分场景内容截图无法显示 | 兼容性相对较好 |
可操作性 | 弱 | 弱 | 强(支持数据脱敏/加密等) |
回放清晰度 | 录制时决定,有损录制 | 录制时决定,有损录制 | 高保真 |
在获取页面的录屏内容之后,这只是第一步,更重要的是我们能够利用这些录屏信息获取到什么信息,分析出什么内容?
#应用场景说明1产品功能分析产品在功能的上线后仅通过点击或PV埋点来判断使用情况是不够的,更应该关注于一些关键页面/功能的真实使用场景,通过用户行为录制将用户的操作路径记录下来,通过回放种子用户的操作进行分析或利用算法对使用路径进行分析能够更好了解功能设计的情况,并帮助进一步优化。2用户访谈记录产品在对用户进行访谈时通过整理用户口述记录和回放访谈录音等方式来进行分析和研究,整体访谈的成本较高,信息利用率也较低,而通过用户行为录制记录下来访谈过程中用户真实的操作记录能够更好地帮助产品来回顾访谈用户的操作习惯。3问题现场复现解决问题第一步就需要复现问题,但有时候问题的复现操作是极其隐蔽的或者由于用户的使用环境等因素很难定位,通过录制用户行为来保存报错时刻的上下文使得我们能够更好地了解用户报错的操作,最大程度减少沟通和内容传递的成本。4自动化测试用例通常自动化测试用例的编写和维护都由人工手动来完成,成本相对比较高,后期维护也不方便,通过用户行为录制将录制的数据加以转换就可以更加快捷方便地进行测试用例的采集,同时也便于管理。5其他还有案例复盘、行为监控/监管、业务流程质检...
Sentry 平台 提供了录制与回放功能用于进行分析,其应用重点在于查看错误或性能问题发生之前、期间和之后的操作情况,其录制与回放功能同样基于 rrweb 库进行开发。
Hotjar 平台提供了录制与回放功能用于进行分析,其除了录制与回放功能外,其提供了页面热力图等分析能力,以帮助用户更好地了解产品的情况,官方也提供了 Demo 可体验 。
其他相关的商业化平台还有LogRocket、FullStory、marker.io等等。
目前,用户行为录制已经在各个场景中广泛使用,例如用户调研、产品分析、Bug回溯、自动化测试和行为监控等等。视频录制、页面截图和 Dom 快照录制等技术方案各有优劣,在面对不同的使用场景时要选择合适的技术方案,但总体而言,以 rrweb 库为代表的 Dom 快照录制技术是目前最为广泛使用也最具优势的技术方案,在各种商业化解决方案中也主要采用该技术方案或思路来实现。
本文由哈喽比特于1年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/kdbhD01m_xgb9jnk5uvtew
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。