配景
公司需要对用户的页面交互进行可回溯记录,固使用rrweb进行需求实现;
先容
rrweb 全称 'record and replay the web',是当下很流行的一个录制屏幕的开源库。与我们传统认知的录屏方式(如 WebRTC)不同的是,rrweb 录制的不是真正的视频流,而是一个记录页面 DOM 变革的 JSON 数组,因此不能录制整个显示器的屏幕,只能录制欣赏器的一个页签。
rrweb的github地址:rrweb/guide.zh_CN.md at master · rrweb-io/rrweb · GitHub
功能实现
- npm install --save rrweb
- npm install --save rrweb-player
复制代码
- 创建screen-record.js文件对上报方法进行封装
rrweb 提供了一个基于 fflate 的简单压缩函数,在提交录制中可以作为 packFn 传入使用。
可以将录制流程分为不同的节点(stepIndex),与服务端商量好进行时间轴排序存储
- import { record, pack, unpack } from 'rrweb'
- import { saveTCDataByXhr, saveTCDataByBeacon, getTcKey, saveTCDataByFetch } from '@/api/record'
- /**
- * 开始屏幕录制
- */
- export function startRecord() {
- if (typeof window.stopRecordFn === 'function') {
- window.stopRecordFn()
- }
- window.screenRecords = []
- console.log('=========== Record Start ==========')
- const stopFn = record({
- emit(event) {
- window.screenRecords.push(event)
- },
- packFn: pack, // rrweb 内包含了基于 fflate 的简单压缩 rrweb.pack,在录制时可以作为 packFn 传入。
- inlineStylesheet: false,
- sampling: {
- scroll: 150, // 每 150ms 最多触发一次
- // set the interval of media interaction event
- media: 800,
- // input: 'last' // 连续输入时,只录制最终值
- },
- })
- window.stopRecordFn = stopFn
- }
复制代码
由于一般提交页面录制数据的时机为页面离开的时机,当欣赏器中的某个页面发生停止时,不能包管进程中的HTTP请求会成功(请参阅有关“停止”和页面生命周期的其他状态的更多信息)。以是我们有如下几个解决方法:
- 使用async/await阻塞异步请求,直至请求发送完毕再进行路由跳转,但似乎这种方法牺牲了用户体验;
- 使用fetch请求的keepalive标识进行数据提交,但是会有欣赏器兼容性题目,其兼容性如下图:
- 使用Navigator.sendBeacon(),该函数专门用于发送单向请求(信标)。但是此 API 不答应您发送自定义标头。因此,为了让我们以“application/json”的情势发送数据,我们需要做一些小调整并使用Blob:
- /**
- * beacon上传可回溯录制数据
- */
- export function saveTCDataByBeacon(data) {
- const url = `${RECORD_BASE_API}/tc/groOrder/saveTcData`
- // let data = new FormData();
- // for (let key in params) {
- // data.append(key, params[key]);
- // }
- const headers = {
- type: 'application/json'
- }
- const blob = new Blob([JSON.stringify(data)], headers)
- return navigator.sendBeacon(url, blob)
- }
复制代码 但是注意:beacon只能提交少量数据,chrome限定最高64KB
基于上诉思路我们封装提交录制方法:
- /**
- *
- * @param stepIndex 录制节点 立即投保: 100, 暂存: 200, 提交审核: 300, 支付: 400, 支付成功: 500
- * @param goodsCode
- * @param submitType 提交数据的方式 默认xhr,页面返回beacon
- */
- export async function submitRecord(stepIndex, linkNo, submitType = 'xhr') {
- if (!window.screenRecords || !window.screenRecords.length) return
- const startEvent = unpack(window.screenRecords[0])
- const endEvent = unpack(window.screenRecords[window.screenRecords.length - 1])
- const { data: tcKey } = await getTcKey({ linkNo })
- const dataString = JSON.stringify(window.screenRecords)
- window.screenRecords.length = 0
- const params = {
- linkNo,
- tcKey,
- stepIndex,
- startTime: startEvent.timestamp,
- endTime: endEvent.timestamp,
- data: dataString,
- }
- // if (stepIndex >= 60) {
- // params.linkNo = linkNo
- // delete params.tcKey
- // }
- return new Promise((reslove, reject) => {
- // @ts-ignore
- if (navigator && navigator.sendBeacon && submitType === 'beacon') {
- // beacon只能提交少量数据,chrome限制最高64KB
- const result = saveTCDataByBeacon(params)
- if (result) {
- console.log('回溯数据请求成功排队 等待执行')
- reslove()
- } else {
- console.log('回溯数据提交失败')
- reject('回溯数据提交失败')
- }
- } else if (submitType === 'fetch') {
- saveTCDataByFetch(params).then(() => {
- reslove()
- }).catch(e => reject(e))
- } else {
- saveTCDataByXhr(params).then(() => {
- reslove()
- }).catch(e => reject(e))
- }
- })
- }
复制代码- /**
- * xhr上传可回溯录制数据
- */
- export function saveTCDataByXhr(data) {
- return request({
- url: `${RECORD_BASE_API}/tc/groOrder/saveTcData`,
- method: 'post',
- data,
- })
- }
- /**
- * Fetch上传可回溯录制数据 将 keepalive 设置为 true 就可确保浏览器关闭或回退,调用接口的链接不会被关闭,调用成功
- */
- export function saveTCDataByFetch(data) {
- const url = `${RECORD_BASE_API}/tc/groOrder/saveTcData`
- const headers = {
- 'Content-Type': 'application/json;charset=UTF-8',
- [REQUEST_TOKEN_KEY]: getToken()
- }
- return new Promise((resolve, reject) => {
- fetch(url, {
- method: 'POST',
- headers,
- body: JSON.stringify(data),
- // keepalive: true,
- })
- .then(res => {
- resolve(res.json())
- })
- .catch(error => {
- reject(error)
- })
- })
- }
复制代码 页面使用
开始录制:
提交录制数据:
- submitRecord(300, this.baseInfo.grpLinkNo)
复制代码 页面回放:
- </template>
- <!-- 回放容器 -->
- <div ref="replayContainer" class="replay-container flex justify-center" />
- </template>
- <script setup>
- import { ref, onMounted} from 'vue';
- import RrwebPlayer from 'rrweb-player';
- import { unpack } from 'rrweb';
- const replayContainer = ref();
- const events = ref([]) // 回放数据通过服务端获取
-
- onMounted(() => {
- new RrwebPlayer({
- target: replayContainer.value,
- unpackFn: unpack,
- props: {
- events: events, // 包含回放所需的数据
- skipInactive: true, // 是否快速跳过无用户操作的阶段
- autoPlay: false, // 是否自动播放
- UNSAFE_replayCanvas: true, // 回放时是否回放 canvas 内容,开启后将会关闭沙盒策略,导致一定风险
- mouseTail: false, // 是否在回放时增加鼠标轨迹
- },
- })
- })
- })
- </script>
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |