阅读视图
🎥解决前端 “复现难”:rrweb 录制回放从入门到精通(上)
一、为什么需要前端录制回放技术?
Hello~大家好。我是秋天的一阵风
前端开发的核心痛点之一,是 “问题不可见”—— 当用户反馈 “按钮点击无响应”“页面空白” 时,开发者只能靠有限日志推测场景,难以复现真实操作路径。
这种信息差不仅拖慢排查效率,更可能让关键业务故障持续影响用户体验。而前端录制回放技术,正是通过精准复现 “操作 - 状态 - 结果” 全链路,为问题诊断、行为分析提供 “上帝视角”,打破 “事后难追溯” 的困境。
从实际业务看,其价值在多场景中尤为突出:
-
电商支付环节:用户反馈 “支付后订单未生成但余额已扣”,传统日志无法确认是否因网络延迟导致重复点击,而录制回放可快速定位 “多次提交引发的前端状态混乱”,排查时间从小时级缩至分钟级;
-
企业 SaaS 运维:CRM 系统 “客户资料保存后字段丢失”,因涉及多模块联动且仅触发于特定操作顺序,传统排查陷入僵局,录制回放直接发现 “标签页快速切换导致组件未提交数据就销毁” 的边缘场景;
-
金融风控合规:录制转账、实名认证等关键操作的页面状态,满足监管 “操作可追溯” 要求;
-
远程技术支持:客服无需依赖用户描述,通过录制文件直观了解故障,大幅提升支持效率。
这些场景的共性,是对 “前端行为可观测性” 的极致需求。传统监控(错误日志、性能上报)仅能提供碎片化信息,而录制回放构建了完整链路,让前端问题从 “难以复现” 变为 “精准可追溯”。rrweb 这类工具的出现,正是为中高级前端工程师提供了应对复杂场景的全新技术方案。
二、 rrweb 介绍:核心概念和特性
2.1 rrweb 的定义与核心定位
rrweb(Record and Replay the Web)是开源的前端录制回放工具,核心定位是 “轻量级、高保真、可扩展的 Web 页面录制与回放解决方案”。它并非通过视频流录制,而是捕获 DOM 变化、用户交互与页面状态,生成结构化数据,再基于数据重建操作流程 —— 这种 “数据化录制” 模式,使其在体积、兼容性、可扩展性上远超传统方案,尤其适配 Vue3、React 等复杂项目的问题排查、行为分析与异常监控。
从架构看,rrweb 由三大核心模块构成:
- rrweb:核心录制库,负责捕获页面变化与事件;
- rrweb-snapshot:DOM 快照处理库,将 DOM 序列化为可传输、可重建的数据;
- rrweb-player:回放组件库,提供录制数据的回放与交互功能。
三者既可独立使用(如用rrweb-snapshot单独处理 DOM 序列化),也可组合成完整链路,这种模块化设计是其适配现代框架生态的关键。
2.2 与传统方案的对比:为什么选择 rrweb?
rrweb 出现前,前端录制主要依赖 “截图拼接”“视频录制”,但两者均存在明显局限,难以满足中高级工程师对 “精准性、轻量性、可分析性” 的需求:
2.2.1 视频录制方案的缺陷
基于 Chrome 插件或客户端工具的视频录制,核心问题是 “重” 与 “不可分析”:
- 体积庞大:1 分钟操作约生成数 10MB 文件,大规模监控会激增带宽与存储成本;
- 无法深析:仅能提供视觉证据,无法关联 DOM 结构、JS 变量状态 —— 例如看到 “按钮点击无反应”,却不知是事件未绑定、接口报错还是状态管理异常;
- 性能影响:对设备配置要求高,低配置设备可能出现卡顿,干扰用户操作。
2.2.2 截图拼接方案的不足
定时截图拼接成 “伪回放”,虽解决体积问题,但牺牲了 “连续性” 与 “高保真”:
- 间隔难平衡:短间隔体积大,长间隔易丢失关键操作;
- 交互缺失:无法复现鼠标悬停、输入框实时输入等动态行为;
- 技术信息断层:无 DOM 树、CSS 样式数据,若问题源于 DOM 结构异常(如嵌套错误、样式覆盖),完全无法定位根源。
2.2.3 rrweb 的核心优势
相比之下,rrweb 的 “数据化录制” 完美规避上述问题,核心优势集中在四点:
- 轻量:JSON 格式数据,体积仅为视频的 1/100 ~ 1/10(1 分钟操作约 100KB~1MB),无传输与存储负担;
- 高保真:精准复现 DOM 变化、用户交互与页面状态,甚至能还原 “网络延迟 loading”“组件异步渲染顺序” 等细节;
- 可分析:录制数据包含完整 DOM 序列化信息与事件日志,回放时可查看任意时刻的 DOM、CSS,还能关联 Vuex/Redux 状态,实现 “现象→根源” 直接跳转;
- 高兼容:支持 Chrome、Firefox、Safari 10+,对框架无侵入 ——Vue3、React、jQuery 项目均可简单集成,无需修改业务代码。
2.3 rrweb 的核心特性
除对比优势外,rrweb 还提供定制化特性,精准解决复杂项目痛点:
2.3.1 隐私保护机制
支持通过配置过滤敏感信息,可设 “黑名单”(指定不录制元素)或 “白名单”(仅录特定区域),满足金融、医疗行业合规要求。例如登录页可通过ignoreElements过滤密码输入框,避免敏感数据泄露。
2.3.2 增量录制与断点续录
- 增量录制:先生成首屏 DOM 快照,后续仅捕获增量变化(如元素新增、属性修改),大幅缩减数据量;
- 断点续录:网络中断恢复后可继续录制,无需重启,适配表单填写等长时间操作场景。
2.3.3 框架友好性
针对 Vue3、React 做专项适配,避免虚拟 DOM 更新导致的录制偏差:
- Vue3 项目中,能识别组件onMounted周期,捕获 DOM 最终状态,而非虚拟 DOM 未挂载的临时结构;
- 支持配置customEvent捕获框架自定义事件(如 Vue 的emit),让录制数据贴合开发逻辑。
2.3.4 可扩展插件体系
提供丰富接口,支持开发自定义插件:
- 性能监控插件:同步捕获 FCP、LCP 等指标;
- 接口关联插件:将 Axios/fetch 日志与时间轴绑定,复现 “接口报错时的页面状态”;
- 错误捕获插件:自动关联window.onerror错误与录制片段,实现 “错误 - 操作” 精准匹配。
这些特性从隐私合规、框架适配到扩展能力,全面覆盖中高级工程师的实际需求,让 rrweb 不仅是 “录制工具”,更是前端可观测性体系的核心组件。
三、快速上手 - 实现基础录制回放
本章将基于实际代码,从依赖安装、核心配置解析、录制功能实现、回放功能落地四个环节,带你完成 rrweb 基础录制回放的全流程,所有代码均可直接复用,同时深入讲解关键配置的设计逻辑,帮助你理解 “为什么这么配” 而非单纯 “怎么配”。
3.1 前置准备:依赖安装与环境配置
rrweb 的录制回放能力依赖三个核心包,但实际项目中无需单独安装 rrweb-snapshot(rrweb 已内置依赖),只需显式安装 rrweb(录制核心)和 rrweb-player(回放组件)即可,同时需引入播放器样式保证 UI 正常渲染。
3.1.1 依赖安装命令
根据项目包管理器选择对应命令,推荐使用 pnpm 或 npm(yarn 命令类似,将 pnpm install 替换为 yarn add 即可):
# 方案1:使用 pnpm 安装(推荐,速度更快且依赖树更扁平)
pnpm install rrweb rrweb-player rrweb-snapshot
# 方案2:使用 npm 安装
npm install rrweb rrweb-player rrweb-snapshot
注意:rrweb-snapshot 虽可被 rrweb 自动引入,但显式安装可避免版本兼容问题(尤其是在多人协作项目中,锁定版本能减少 “我这能跑他那报错” 的情况)。
3.1.2 模块引入
在 Vue3 组件(或 React 组件、原生 JS 文件)中引入所需模块,需特别注意 rrweb-player 的样式文件必须引入,否则回放控制器会出现布局错乱:
// 1. 引入录制核心函数(从 rrweb 中导出)
import { record } from 'rrweb';
// 2. 引入回放组件(rrweb-player 默认导出类)
import rrwebPlayer from 'rrweb-player';
// 3. 引入回放组件样式(关键!否则控制器无样式)
import 'rrweb-player/dist/style.css';
// 4. Vue3 项目需引入响应式API(非Vue项目可忽略,用普通变量存储状态)
import { ref } from 'vue';
3.2 核心状态管理:定义关键变量
在实现功能前,需先定义存储 “录制状态” “录制数据” “配置项” 的变量,这些变量是连接 “录制” 与 “回放” 的核心纽带。以下以 Vue3 响应式变量为例:
// 1. 存储录制的事件数组(rrweb 录制的核心数据,回放时需传入此数组)
const events = ref([]);
// 2. 存储录制配置项(用对象统一管理,便于后续扩展和修改)
const recordConfig = ref({
maskAllInputs: true, // 隐私保护:是否对所有输入框内容脱敏
maskInputPassword: true, // 隐私保护:单独控制密码输入框脱敏(优先级高于 maskAllInputs)
blockClass: 'rrweb-block', // 隐私保护:带此类名的元素会被完全屏蔽(不录制其内容)
recordCanvas: false, // 功能配置:是否录制 Canvas 内容(开启会增加数据量)
samplingInterval: 100, // 性能优化:滚动事件采样间隔(单位ms,0表示不采样)
});
// 3. 录制状态标识(控制按钮显示/隐藏、防止重复录制)
const isRecording = ref(false);
// 4. 是否有录制数据(控制回放按钮是否可用)
const hasRecording = ref(false);
// 5. 存储停止录制的函数(rrweb.record() 会返回停止函数,需保存以便后续调用)
let stopFn = null;
// 6. 存储回放实例(控制回放的暂停、销毁等操作)
let player = null;
// 7. 回放容器DOM引用(需绑定到页面中的回放容器元素)
const replayContainer = ref(null);
设计思路:将配置项用 recordConfig 统一管理,而非散落在代码中,既能提高可读性,也便于后续开发 “配置修改面板”(如让产品经理通过 UI 调整采样间隔)。
3.3 实现录制功能:从配置到启停
录制是 rrweb 的核心能力,核心逻辑是调用 rrweb.record(options) 生成录制实例,通过 options.emit 收集 DOM 变化事件,同时通过配置项平衡 “录制精度”“数据体积”“隐私安全”。
3.3.1 核心录制函数(startRecord)
// 开始录制函数:整合配置、创建录制实例、收集事件
const startRecord = () => {
// 1. 初始化状态:清空历史录制数据,避免与新数据混淆
events.value = [];
console.log('### 开始录制,当前配置:', recordConfig.value);
// 2. 配置录制选项(rrweb 的核心配置,每一项都影响录制效果)
const options = {
/**
* 关键回调:每次DOM变化/用户交互时触发
* @param {Object} event - rrweb 生成的事件对象(包含事件类型、目标元素、时间戳等)
* 作用:收集所有事件到 events 数组,为后续回放提供数据
*/
emit(event) {
events.value.push(event);
},
// -------------------------- 隐私保护配置 --------------------------
/**
* maskAllInputs:是否对所有输入框(input/textarea)内容脱敏
* 效果:输入框内容会被替换为 "●●●",避免录制手机号、密码等敏感信息
* 场景:适用于整站录制,无需区分输入框类型的场景
*/
maskAllInputs: recordConfig.value.maskAllInputs,
/**
* maskInputOptions:精细化控制输入框脱敏(优先级高于 maskAllInputs)
* 支持的键:password/email/number/tel/text 等 input 类型
* 场景:需要“密码脱敏但普通文本不脱敏”的场景(如仅保护密码框)
*/
maskInputOptions: {
password: recordConfig.value.maskInputPassword,
},
/**
* blockClass:指定“完全屏蔽”的类名
* 效果:带此类名的元素会被录制为“黑色块”,内容和交互都不记录
* 场景:屏蔽广告、隐私弹窗、用户头像等绝对不能录制的元素
* 使用方式:在DOM元素上添加 class="rrweb-block" 即可
*/
blockClass: recordConfig.value.blockClass,
// -------------------------- 功能配置 --------------------------
/**
* recordCanvas:是否录制 Canvas 内容
* 原理:通过截取 Canvas 帧并转为 base64 存储(会大幅增加数据量)
* 注意:若页面有高频刷新的 Canvas(如游戏、图表),不建议开启
*/
recordCanvas: recordConfig.value.recordCanvas,
/**
* inlineStylesheet:是否将样式表内联到录制数据中
* 效果:回放时无需依赖原页面的CSS文件,保证样式一致性
* 必要性:若回放环境与录制环境样式不同(如测试服vs生产服),必须开启
*/
inlineStylesheet: true,
// -------------------------- 性能优化:采样配置 --------------------------
/**
* sampling:控制高频事件的录制频率,减少数据体积
* 适用事件:mousemove(鼠标移动)、scroll(滚动)、mouseInteraction(鼠标交互)
* 设计逻辑:高频事件(如滚动)无需每帧记录,间隔100ms记录一次即可满足回放需求
*/
sampling: recordConfig.value.samplingInterval > 0 ? {
mousemove: true, // 鼠标移动:开启采样(默认每100ms记录一次)
mouseInteraction: true, // 鼠标交互(点击、悬停):开启采样
scroll: recordConfig.value.samplingInterval, // 滚动事件:按配置间隔采样
input: 'all' // 输入事件:记录所有(不采样,避免丢失输入内容)
} : {
input: 'all' // 若采样间隔为0,仅记录输入事件(适用于对数据体积要求极高的场景)
}
};
// 3. 调用 rrweb.record() 开始录制,返回“停止函数”并保存
// 注意:每次调用 record() 都会生成新实例,需先停止旧实例再开启新录制
stopFn = record(options);
// 4. 更新状态:控制UI显示(如录制按钮变“停止按钮”)
isRecording.value = true;
hasRecording.value = false;
};
3.3.2 停止录制函数(stopRecord)
停止录制的逻辑相对简单,核心是调用 startRecord 中保存的 stopFn,并更新状态标识:
const stopRecord = () => {
// 防止重复调用:若 stopFn 不存在(未开始录制),直接返回
if (!stopFn) return;
// 调用停止函数:销毁录制实例,停止收集事件
stopFn();
// 清空 stopFn:避免后续误调用
stopFn = null;
// 更新状态:录制状态置为false,判断是否有有效录制数据
isRecording.value = false;
hasRecording.value = events.value.length > 0;
// 日志输出:便于调试(如录制数据量异常时,可查看事件数量)
console.log(`### 停止录制,共收集 ${events.value.length} 个事件`);
};
调试技巧:若发现录制后 events 数组为空,可检查是否有以下问题:
页面无 DOM 变化 / 交互;
maskAllInputs等配置误屏蔽了所有元素;组件销毁导致 emit 回调未执行。
3.4 实现回放功能:基于录制数据重建场景
回放的核心是通过 rrweb-player 类创建回放实例,将 events 数组传入,让播放器按时间戳依次执行事件,重建录制时的页面场景。
3.4.1 核心回放函数(replay)
const replay = () => {
// 防护逻辑:若无录制数据,直接返回(避免报错)
if (events.value.length === 0) {
console.warn('### 无录制数据,无法回放');
return;
}
// 清理旧实例:若已有回放实例,先暂停并销毁(避免多实例冲突)
if (player) {
player.pause(); // 暂停回放
player = null; // 清空实例引用(触发垃圾回收)
}
// 创建回放实例:核心是传入录制的 events 数组和容器
player = new rrwebPlayer({
/**
* target:回放容器DOM元素
* 要求:容器必须已挂载到DOM树(Vue3中需用 ref 绑定,确保不是 null)
* 建议:容器设置固定宽高(如 width: 100%; height: 600px),避免回放时变形
*/
target: replayContainer.value,
/**
* props:回放配置项(控制播放行为和UI)
*/
props: {
events: events.value, // 核心数据:录制的事件数组(必须传入)
width: replayContainer.value.clientWidth, // 回放宽度(与容器一致,避免拉伸)
height: replayContainer.value.clientHeight, // 回放高度(与容器一致)
autoPlay: true, // 自动播放:无需手动点击“播放”按钮
showController: true, // 显示控制器:包含播放/暂停、速度调节、进度条
speedOption: [1, 2, 4, 8], // 播放速度选项:支持1x(正常)、2x(倍速)等
showTime: true, // 显示录制时间戳(便于定位特定时间点的问题)
},
});
};
3.4.2 页面 UI 绑定(Vue3 模板示例)
回放功能需要绑定 “录制 / 停止 / 回放按钮” 和 “回放容器”,以下是 Vue3 模板代码,可直接整合到组件中:
<template>
<div class="rrweb-demo">
<!-- 1. 操作按钮区域:控制录制启停和回放 -->
<div class="control-buttons">
<!-- 录制/停止按钮:根据 isRecording 切换文本和事件 -->
<button @click="isRecording ? stopRecord() : startRecord()">
{{ isRecording ? '停止录制' : '开始录制' }}
</button>
<!-- 回放按钮:仅当有录制数据时可用 -->
<button @click="replay()" :disabled="!hasRecording">
开始回放
</button>
</div>
<!-- 2. 回放容器:必须设置宽高,绑定 ref 供 JS 调用 -->
<div class="replay-container" ref="replayContainer"></div>
</div>
</template>
<style scoped>
.rrweb-demo {
padding: 20px;
}
.control-buttons {
margin-bottom: 20px;
gap: 10px;
display: flex;
}
.control-buttons button {
padding: 8px 16px;
cursor: pointer;
}
/* 回放容器:固定高度,添加边框便于识别 */
.replay-container {
width: 100%;
height: 600px;
border: 1px solid #e5e7eb;
border-radius: 4px;
overflow: hidden;
}
</style>
3.5 验证与调试:确保功能正常运行
完成代码编写后,需通过简单操作验证功能是否正常,同时掌握常见问题的调试方法:
3.5.1 功能验证结果
3.5.2 常见问题调试
- 问题 1:回放容器空白
排查方向:
- 检查
replayContainer是否绑定正确(控制台打印replayContainer.value确认不是null); - 检查
events数组是否有数据(若为空,需检查startRecord的emit回调是否执行)。
- 问题 2:输入框内容未脱敏
排查方向:
- 确认
maskAllInputs或maskInputPassword配置为true; - 检查输入框是否有
rrweb-block类(此类会完全屏蔽元素,而非脱敏)。
- 问题 3:回放数据体积过大
优化方向:
- 开启
sampling配置(如 samplingInterval: 100); - 关闭
recordCanvas(若页面无必要录制的 Canvas); - 通过
blockClass屏蔽无需录制的区域(如广告栏)。
四、高级功能详解
在基础录制回放功能之上,rrweb 提供的高级特性是其适配企业级场景的核心能力 —— 从用户隐私保护到录制性能优化,再到录制数据的全生命周期管理,这些功能直接决定了 rrweb 在实际项目中的可用性与安全性。
4.1 隐私保护机制(输入掩码、元素屏蔽)
前端录制本质是对用户操作与页面内容的 “全量捕获”,但金融、医疗、电商等场景中,页面常包含身份证号、银行卡号、密码等敏感信息,若直接录制会引发隐私合规风险。 rrweb 提供的 “输入掩码” 与 “元素屏蔽” 机制,正是为了解决这一核心痛点,实现 “精准录制” 与 “隐私保护” 的平衡。
4.1.1 输入掩码:定向隐藏敏感输入内容
输入掩码功能通过配置,让 rrweb 在录制时自动将指定类型的输入内容替换为占位符(如 “*”),回放时仅显示占位符,不泄露真实数据。核心适配两类场景:“全量输入掩码”(隐藏所有输入框内容)与 “定向输入掩码”(仅隐藏密码等特定类型输入)。
代码实现与配置解析
// 录制配置中的隐私保护选项(核心配置)
const recordConfig = ref({
maskAllInputs: false, // 全局开关:是否掩码所有输入字段
maskInputPassword: true, // 定向开关:仅掩码密码类型输入框
});
// 集成到 rrweb 录制选项中
const options = {
// 其他配置...
maskAllInputs: recordConfig.value.maskAllInputs, // 全局掩码开关
maskInputOptions: {
password: recordConfig.value.maskInputPassword, // 密码字段单独控制
},
};
在 Vue 模板中,通过开关组件让用户(或开发者)动态控制掩码规则:
<el-form-item label="隐私保护">
<div class="config-group">
<!-- 全局输入掩码开关:开启后所有输入框内容回放时显示为 * -->
<el-switch
v-model="recordConfig.maskAllInputs"
:disabled="isRecording" <!-- 录制中不可修改配置 -->
active-text="掩码所有输入"
/>
<!-- 密码定向掩码开关:仅对 type="password" 的输入框生效 -->
<el-switch
v-model="recordConfig.maskInputPassword"
:disabled="isRecording"
active-text="掩码密码输入"
/>
</div>
</el-form-item>
效果说明与应用场景
- 当
maskInputPassword: true时:所有 输入框的内容,在录制数据中会被替换为 “*”,回放时用户看不到真实密码(如登录页、支付密码输入场景);
- 当
maskAllInputs: true时:无论输入框类型(文本、手机号、银行卡号),内容都会被掩码,适用于 “页面包含多种敏感输入” 的场景(如金融 App 的实名认证页面、医疗系统的患者信息填写页)。
最佳实践建议
- 优先使用 “定向掩码” 而非 “全局掩码”:全局掩码会隐藏所有输入内容,可能导致排查问题时无法获取关键操作(如用户输入的搜索关键词),建议仅对敏感字段单独配置;
- 结合业务场景预设默认值:例如电商支付页默认开启 “密码掩码”,普通信息填写页默认关闭全局掩码;
- 录制前提示用户:若录制涉及用户操作,需在开始录制前告知 “敏感输入会被掩码处理”,避免用户隐私顾虑。
4.1.2 元素屏蔽:整体隐藏敏感区域
除了输入内容,页面中可能存在整体敏感区域(如用户头像、收货地址卡片、医疗报告详情),此时需要 “元素屏蔽” 功能 —— 通过给元素添加指定类名,让 rrweb 在录制时直接将该元素替换为灰色块,回放时完全看不到原始内容。
代码实现与配置解析
首先在配置中定义屏蔽类名:
const recordConfig = ref({
blockClass: 'rr-block', // 屏蔽元素的类名(可自定义)
});
// 集成到 rrweb 录制选项
const options = {
// 其他配置...
blockClass: recordConfig.value.blockClass, // 指定屏蔽类名
};
在 Vue 模板中,给敏感元素添加该类名即可实现屏蔽:
<!-- 普通内容:无屏蔽类名,正常录制 -->
<div style="padding: 16px; background: #f0f9ff;">
<strong>普通订单信息</strong>
<p>订单号:202405201234567</p>
<p>商品:前端开发实战教程</p>
</div>
<!-- 敏感内容:添加 rr-block 类名,录制时被屏蔽 -->
<div class="rr-block" style="padding: 16px; background: #fef2f2; margin-top: 16px;">
<strong>收货地址(敏感信息)</strong>
<p>收件人:张三</p>
<p>电话:138****5678</p>
<p>地址:北京市朝阳区XX小区1号楼1单元101</p>
</div>
效果说明与应用场景
- 录制时:rrweb 会检测所有带 rr-block 类名的元素,将其内容替换为 “灰色占位块”,且不会捕获该元素内部的任何 DOM 变化与输入事件;
- 回放时:用户看到的是灰色块,完全无法获取屏蔽区域的原始内容,适用于 “整段内容均敏感” 的场景(如用户个人信息卡片、企业内部数据报表、医疗诊断结果)。
最佳实践建议
- 屏蔽类名避免与业务类名冲突:建议使用前缀(如 rr-),防止误屏蔽正常业务元素;
- 屏蔽区域不宜过大:若将整个页面的大部分区域屏蔽,会导致录制数据失去排查价值,建议仅对 “必要敏感区域” 使用;
- 动态添加屏蔽类名:对于 Vue 组件,可通过 :class="{ 'rr-block': isSensitive }" 实现 “条件屏蔽”(如仅当用户角色为 “普通用户” 时屏蔽管理员数据)。
4.2 录制配置优化(采样率、Canvas 录制)
在复杂页面(如包含大量动画、Canvas 绘图、滚动操作的页面)中,默认录制配置可能导致 “数据量过大”“性能消耗高” 等问题。rrweb 提供的 “采样率控制” 与 “Canvas 录制开关”,可帮助开发者在 “录制精度” 与 “性能 / 数据量” 之间找到最佳平衡点。
4.2.1 采样率控制:减少冗余录制数据
页面中的高频事件(如鼠标移动、滚动)会产生大量冗余数据(例如 1 秒内可能触发数十次滚动事件),但多数场景下无需记录每一次事件。
通过 “采样率配置”,可控制这类事件的录制频率,大幅减少数据量。
代码实现与配置解析
const recordConfig = ref({
samplingInterval: 200, // 采样间隔(单位:ms),0表示不限制
});
// 集成到 rrweb 录制选项
const options = {
// 其他配置...
sampling: recordConfig.value.samplingInterval > 0 ? {
mousemove: true, // 开启鼠标移动事件采样
mouseInteraction: true, // 开启鼠标交互(点击、hover)事件采样
scroll: recordConfig.value.samplingInterval, // 滚动事件采样间隔
input: 'all' // 输入事件不采样(记录所有输入,确保数据完整)
} : {
input: 'all' // 采样间隔为0时,仅确保输入事件完整
},
};
在 Vue 模板中,通过数字输入框动态调整采样间隔:
<el-form-item label="采样间隔 (ms)">
<el-input-number
v-model="recordConfig.samplingInterval"
:disabled="isRecording"
:min="0"
:max="5000" <!-- 最大间隔5秒,避免数据丢失过多 -->
:step="100" <!-- 步长100ms,便于精细调整 -->
/>
<span class="config-hint">0 表示不限制,数值越大录制数据越少(推荐200-500ms)</span>
</el-form-item>
效果说明与应用场景
- 当 samplingInterval: 200 时:滚动事件每 200ms 仅记录 1 次,1 秒内最多记录 5 次,相比不采样(可能数十次),数据量减少 70% 以上;
- 鼠标移动事件:采样后仅记录关键移动轨迹点,而非每一个像素的移动,既保证回放时 “视觉流畅”,又减少数据量;
- 适用场景:包含长列表滚动(如电商商品列表)、复杂动画(如数据可视化图表)、高频鼠标操作(如绘图工具)的页面。
最佳实践建议
- 推荐采样间隔:200-500ms 是 “数据量” 与 “回放精度” 的平衡点,低于 200ms 数据量下降不明显,高于 500ms 可能导致回放时 “操作跳跃”;
- 输入事件不采样:input: 'all' 必须保留,因为输入内容(如用户填写的表单)是排查问题的关键,采样会导致内容缺失;
- 动态调整采样率:可根据页面类型自动切换(如列表页采样间隔 300ms,表单页采样间隔 0ms,确保输入完整)。
4.2.2 Canvas 录制:按需捕获绘图内容
Canvas 绘图(如游戏、数据可视化图表、在线绘图工具)的录制需要特殊处理 —— 默认情况下,rrweb 不会捕获 Canvas 内部的像素变化,回放时只能看到 Canvas 初始状态。通过 recordCanvas 配置,可开启 Canvas 录制,但需注意其对性能与数据量的影响。
代码实现与配置解析
const recordConfig = ref({
recordCanvas: true, // 是否录制 Canvas 内容(默认关闭)
inlineImages: false // 是否内联 Canvas 图片(影响数据量)
});
// 集成到 rrweb 录制选项
const options = {
// 其他配置...
recordCanvas: recordConfig.value.recordCanvas, // Canvas 录制开关
inlineImages: recordConfig.value.inlineImages, // 图片内联开关
};
效果说明与应用场景
- 当
recordCanvas: true时:rrweb 会定期捕获 Canvas 的像素数据(以图片形式),并记录到录制事件中,回放时可完整复现绘图过程(如用户在在线白板上的绘画操作、数据图表的动态渲染过程);
- 当
inlineImages: true时:Canvas 图片会以 Base64 格式内联到 JSON 录制数据中,无需额外加载图片,但会导致数据量大幅增加(1 张 100KB 的图片会让 JSON 体积增加 100KB 以上);
- 适用场景:包含动态 Canvas 内容的页面(如在线绘图工具、游戏 Demo、实时数据可视化大屏),不适用场景:静态 Canvas 图片(如仅展示一张图表,无动态变化)。
最佳实践建议
- 按需开启 Canvas 录制:仅在需要复现 Canvas 动态变化时开启,静态 Canvas 无需录制;
- 谨慎使用图片内联:若 Canvas 变化频繁(如每秒多次刷新),建议关闭 inlineImages,并将图片上传到服务器,录制数据中仅保留图片 URL(需自定义数据处理逻辑),减少 JSON 体积;
- 控制 Canvas 捕获频率:通过 rrweb 的 canvasCaptureInterval 配置(需额外扩展)调整捕获间隔(如 1 秒 1 次),避免高频捕获导致前端卡顿。
4.3 数据管理(下载 / 上传、大小计算)
录制数据的 “存储、传输、复用” 是 rrweb 落地的关键环节 —— 开发者需要将录制数据下载到本地用于调试,或上传到服务器用于团队共享、异常监控分析。本节将详解录制数据的大小计算、下载导出与上传导入功能。
4.3.1 数据大小计算:实时掌握数据体积
录制数据的大小直接影响下载速度、存储成本与传输效率,通过实时计算数据大小,可帮助开发者判断是否需要优化录制配置(如调整采样率、关闭 Canvas 录制)。
代码实现与逻辑解析
// 计算录制数据大小(基于响应式事件数组)
const dataSize = computed(() => {
// 将事件数组序列化为 JSON 字符串,创建 Blob 对象获取大小
const size = new Blob([JSON.stringify(events.value)]).size;
// 单位转换:B → KB → MB
if (size < 1024) return `${size} B`;
if (size < 1024 * 1024) return `${(size / 1024).toFixed(2)} KB`;
return `${(size / 1024 / 1024).toFixed(2)} MB`;
});
效果说明与应用场景
- 实时更新:当
events.value(录制事件数组)变化时,dataSize会自动重新计算,开发者可在界面上实时看到 “当前录制数据大小”;
- 数据量预警:若数据大小超过阈值(如 10MB),可触发提示(如 “数据量过大,建议调整采样率”),避免生成超大文件;
- 适用场景:所有录制场景,尤其适合长时间录制(如用户操作 10 分钟以上)或复杂页面录制。
最佳实践建议
- 显示数据大小位置:将
dataSize显示在 “录制状态” 旁边(如 “录制中 | 数据大小:2.34 KB”),让开发者实时感知;
- 设置数据量阈值预警:通过 watch 监听 dataSize,当超过阈值(如 5MB)时,用 ElMessage 提示 “数据量较大,可能影响下载与回放速度,建议优化配置”;
- 结合采样率联动:若数据量增长过快,可自动建议提高采样间隔(如 “当前数据量增长较快,是否将采样间隔从 200ms 调整为 300ms?”)。
4.3.2 下载录制数据:本地存储与调试
将录制数据下载为 JSON 文件,可用于本地调试(如复现用户反馈的问题)、离线分析(如无网络时查看录制内容)或归档备份(如保存关键异常场景的录制数据)。
代码实现与逻辑解析
// 下载录制数据为 JSON 文件
const downloadRecording = () => {
// 校验数据是否存在
if (events.value.length === 0) {
ElMessage.warning('没有录制数据可下载');
return;
}
// 1. 序列化事件数组(缩进2空格,便于阅读)
const data = JSON.stringify(events.value, null, 2);
// 2. 创建 Blob 对象(指定 JSON 类型)
const blob = new Blob([data], { type: 'application/json' });
// 3. 生成临时 URL
const url = URL.createObjectURL(blob);
// 4. 创建隐藏的 a 标签实现下载
const a = document.createElement('a');
a.href = url;
// 文件名包含时间戳,避免重复(如 rrweb-recording-1716234567890.json)
a.download = `rrweb-recording-${Date.now()}.json`;
document.body.appendChild(a);
a.click(); // 触发下载
// 5. 清理临时资源(避免内存泄漏)
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log('### 下载录制数据,大小:', dataSize.value);
};
效果说明与应用场景
- 下载文件:点击 “下载” 按钮后,浏览器会自动下载一个 JSON 文件,文件内容为结构化的录制事件(包含 DOM 快照、用户交互、时间戳等信息);
- 本地回放:将下载的 JSON 文件重新上传到 rrweb 播放器,可在本地复现录制场景;
- 适用场景:开发者本地调试(复现用户反馈的 bug)、异常场景归档(保存生产环境出现的偶发问题)、跨团队共享(将录制文件发送给其他开发者协助排查)。
最佳实践建议
- 文件名规范:包含 “rrweb-recording” 前缀 + 时间戳,便于识别文件用途与创建时间;若涉及业务场景,可额外添加场景标识(如 “rrweb-recording-20240520-order-fail-1716234567890.json”),快速定位文件对应的问题场景;
- 下载前校验:除了校验数据长度,还可校验数据格式(如是否包含首屏 DOM 快照事件FullSnapshot),避免因数据损坏导致下载的文件无法回放;可添加如下校验逻辑:
const hasValidSnapshot = events.value.some(event => event.type === 2); // FullSnapshot 事件类型为2
if (!hasValidSnapshot) {
ElMessage.error('录制数据无效,缺少首屏快照,无法回放');
return;
}
- 大文件处理:若录制数据超过 10MB,可在下载前提示用户 “文件体积较大,下载可能需要较长时间”,并提供 “取消下载” 选项,避免占用过多带宽;
- 下载状态反馈:添加下载进度提示(如 “正在生成下载文件...”“下载完成”),让用户清晰感知操作进度,提升体验。
4.3.3 上传录制数据:复用与共享
上传录制数据功能可将本地保存的 JSON 文件导入到 rrweb 播放器中,实现 “跨设备回放”“团队共享调试”“异常数据上报” 等场景,是录制数据 “复用” 的核心环节。
代码实现与逻辑解析
上传功能分为 “触发文件选择”“读取文件内容”“解析数据并导入” 三个核心步骤,完整代码如下:
// 1. 触发文件选择(隐藏原生input,通过按钮触发)
const triggerUpload = () => {
// 获取隐藏的文件输入框并触发点击
document.getElementById('file-upload').click();
};
// 2. 处理文件上传逻辑
const uploadRecording = (event) => {
// 获取用户选择的第一个文件(仅支持JSON格式)
const file = event.target.files[0];
if (!file) return;
// 校验文件格式:仅允许JSON文件
if (file.type !== 'application/json' && !file.name.endsWith('.json')) {
ElMessage.error('请上传JSON格式的录制文件');
event.target.value = ''; // 重置输入框,避免重复触发
return;
}
// 3. 读取文件内容(使用FileReader)
const reader = new FileReader();
// 读取成功回调
reader.onload = (e) => {
try {
// 解析JSON内容
const data = JSON.parse(e.target.result);
// 4. 校验录制数据格式
// 要求:数据为数组,且至少包含一个事件(推荐包含首屏快照)
if (!Array.isArray(data) || data.length === 0) {
throw new Error('录制数据为空或格式错误(需为事件数组)');
}
// 可选校验:是否包含首屏快照,确保能正常回放
const hasFullSnapshot = data.some(item => item.type === 2);
if (!hasFullSnapshot) {
ElMessage.warning('录制数据缺少首屏快照,可能无法正常回放');
}
// 5. 导入数据并更新状态
events.value = data; // 将上传的事件数组赋值给响应式变量
hasRecording.value = true; // 标记存在可回放数据
ElMessage.success(`上传成功!共导入 ${data.length} 个录制事件`);
console.log(`### 上传录制数据成功,大小:`, new Blob([e.target.result]).size / 1024.toFixed(2) + ' KB');
} catch (error) {
// 捕获解析错误(如JSON格式错误、数据校验失败)
console.error('### 上传失败:', error);
ElMessage.error(`上传失败:${error.message}`);
}
};
// 读取失败回调
reader.onerror = () => {
ElMessage.error('文件读取失败,请检查文件是否损坏');
};
// 以文本形式读取文件(JSON文件本质是文本)
reader.readAsText(file);
event.target.value = ''; // 重置输入框,允许重复上传同一文件
};
在 Vue 模板中,需要添加隐藏的文件输入框与触发按钮:
<!-- 隐藏的文件输入框:仅用于接收文件选择,不直接显示 -->
<input
id="file-upload"
type="file"
accept=".json" <!-- 仅允许选择JSON文件 -->
style="display: none;"
@change="uploadRecording" <!-- 文件选择变化时触发上传逻辑 -->
/>
<!-- 上传按钮:用户可见,点击触发文件选择 -->
<el-button
type="primary"
icon="el-icon-upload"
@click="triggerUpload"
:disabled="isRecording"
>
上传录制文件
</el-button>
效果说明与应用场景
- 上传流程:用户点击 “上传录制文件” 按钮,选择本地 JSON 格式的录制文件后,系统会自动校验文件格式、解析数据,若校验通过则更新录制事件数组,用户可直接点击 “回放” 按钮查看上传的录制内容;
- 核心场景:
- 跨设备调试:开发者在本地录制用户反馈的 bug 场景,将 JSON 文件上传到测试环境或其他设备,复现问题进行调试;
- 团队共享:将关键异常场景的录制文件上传到团队共享平台(如 Git、云存储),其他开发者下载后可快速复现问题,无需重复搭建场景;
- 异常数据上报:在生产环境中,当前端发生严重异常(如白屏、崩溃)时,自动将录制数据上传到后端监控系统,运维人员可通过上传功能导入数据,分析异常原因;
- 回归测试:将历史 bug 的录制文件保存,每次版本迭代后上传回放,验证 bug 是否复现,确保问题彻底修复。
最佳实践建议
- 严格文件校验:除了格式校验(JSON、.json 后缀),还需校验数据结构(是否为数组、是否包含关键事件类型),避免无效数据导致播放器崩溃;
- 大文件上传优化:若上传文件超过 5MB,建议添加 “分片上传” 逻辑(需后端配合),避免一次性读取大文件导致前端内存溢出;可使用slice方法将文件分片:
// 简单分片示例(需后端支持分片接收)
const chunkSize = 1024 * 1024; // 1MB每片
const totalChunks = Math.ceil(file.size / chunkSize);
for (let i = 0; i < totalChunks; i++) {
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
// 上传分片...
}
- 上传状态反馈:添加 “上传中” loading 状态、上传进度条(如 “已上传 30%”),以及失败重试按钮,提升用户体验;
- 数据安全:若上传的录制数据包含敏感信息(即使已做掩码处理),建议通过 HTTPS 传输,并在后端添加权限控制(如仅团队成员可访问),避免数据泄露。