前端如何靜悄悄錄制用戶的操作過程,靜悄悄上傳到服務器
背景
公司有很多的項目,但是并不是每一個項目都很重要,其實重要的項目就那么幾個,上面也是很重視這幾個項目,尤其是對一些生產問題的關注度很高。
這幾天上面交代下來了,需要對這些項目做一些用戶行為的記錄,主要是為了更好地還原用戶在某一個時間點的操作過程
注意點
想要完成這個需求,仔細想了一下,需要注意幾個點:
- 跨框架使用:這些項目有vue、angular、react,需要都能適用
- 能錄制用戶行為:能把用戶在頁面上的操作錄制下來
- 能回放錄制:如果不能回放,那么這個錄制就無意義了
- 用戶無感知:必須做到用戶無感知才行
思考 & 技術方案
說到前端視頻的錄制,我們會想到 webRTC
這個技術,他能做到錄制屏幕的效果,但是通過 webRTC
去完成這個方案的話,有幾個缺點:
- 做不到用戶無感知,需要用戶同意才能錄制
- 錄制的視頻太大了,太占內存了
- 學習成本比較高,這也是原因之一
那怎么才能做到:
- 用戶無感知
- 不錄制視頻
其實只要不錄制視頻了,那么用戶肯定就無感知,因為一旦要錄視頻,瀏覽器肯定要詢問用戶同意不同意。
所以我們選擇了另一個方案 rrweb
,一個用來錄制用戶頁面行為的庫~
rrweb
rrweb
是 record and replay the web
的簡寫,旨在利用現代瀏覽器所提供的強大 API 錄制并回放任意 web 界面中的用戶操作。
效果展示
基本使用
我們先定義好 html 結構,三個按鈕
- 錄制:點擊開始錄制
- 回放:點擊開始回放
- 返回:點擊重新再來
還有一個 replayer ,用來當做回放的容器
<template>
<div class="main">
<div >
<el-button @click="record">錄制</el-button>
<el-button @click="replay">回放</el-button>
<el-button @click="reset">返回</el-button>
</div>
<div v-if="!showReplay">
<div>
<el-input style="width: 300px" v-model="value" />
</div>
<div>
<el-button>按鈕1</el-button>
</div>
<div>
<el-button>按鈕2</el-button>
</div>
<div>
<el-button>按鈕3</el-button>
</div>
</div>
<div ref="replayer"></div>
</div>
</template>
我們需要先安裝兩個包:npm i rrweb rrwebPlayer
- rrweb:用來錄制網頁的
- rrwebPlayer:用來回放的
rrweb
擁有一個 record
函數來進行錄制操作,并可傳入配置,emit
屬性就是用戶操作的監聽函數,接收一個參數event
,這個參數是什么,我們后面會說~
然后我們定義三個函數:
- record:錄制函數
- replay:回放函數
- reset:返回/重置函數
const rrweb = require("rrweb");
import rrwebPlayer from "rrweb-player";
const events = ref([]);
const stopFn = ref(null);
const showReplay = ref(false);
const replayer = ref(null)
const record = () => {
console.log("開始錄制");
stopFn.value = rrweb.record({
emit: (event) => {
events.value.push(event);
},
// 支持錄制canvas
recordCanvas: true,
collectFonts: true,
});
};
const replay = () => {
stopFn.value();
showReplay.value = true;
new rrwebPlayer({
// 可以自定義 DOM 元素
target: replayer.value,
// 配置項
props: {
// 傳入events
events: events.value,
},
});
};
const reset = () => {
showReplay.value = false;
events.value = []
};
錄的是視頻嗎?
我們之前說了:一旦要錄視頻,瀏覽器肯定要詢問用戶同意不同意。但是我們發現我們使用 rrweb
去錄制,瀏覽器并沒有詢問,做到了無感知~所以我們可以推斷出,rrweb
錄制的并不是視頻,那錄制的是什么呢?
我們其實可以試著去輸出一下剛剛的參數 event
看看是什么
rrweb.record({
emit: (event) => {
// 輸出
+ console.log(event)
events.value.push(event);
},
});
我們可以看到這個event
記錄的東西是當前頁面的DOM
結構,當用戶操作頁面時,rrweb
會將每一次的DOM結構轉換成對象形式,通過 emit
函數的第一個參數輸出,我們使用一個數組去記錄這一次次的DOM結構,然后把它傳給rrweb-player
,它能將這些DOM結構按照先后順序,一個一個展示出來,自然就相當于是視頻的展示效果了~
rrweb
能記錄這些頁面的 DOM 行為:
- 節點創建、銷毀
- 節點屬性變化
- 文本變化
- 鼠標移動
- 鼠標交互
- 頁面或元素滾動
- 視窗大小改變
- 輸入
上傳 & 優化
我們記錄的這些數據,需要上傳到后端那邊去,方便后續在后臺管理系統里管理這些回放~
很多人會說這樣一直錄制,那豈不是數據量很大?所以我覺得只有在一些重要的頁面,才需要做錄制行為的操作,而不是每一個頁面都去做這樣的操作~
并且每次上傳需要一定的時間間隔,不能上傳太頻繁,不然瀏覽器壓力會增大~
const record = () => {
console.log("開始錄制");
stopFn.value = rrweb.record({
emit: (event) => {
events.value.push(event);
},
recordCanvas: true,
collectFonts: true,
});
};
const report = async () => {
await reportRequest(events.value);
events.value = [];
}
// 20s 去上傳一次
setInterval(report, 10 * 2000);
同時,雖然現在上傳的是DOM結構的對象,大小遠遠比視頻小,但是其實還是不小的
所以我們需要采取措施,去壓縮一下數據,壓縮后再進行上傳,這樣能降低服務器的壓力~我們可以使用packFn
屬性來對錄制的數據進行壓縮,同時回放時也要用unpackFn
去解碼
const record = () => {
console.log("開始錄制");
stopFn.value = rrweb.record({
emit: (event) => {
events.value.push(event);
},
recordCanvas: true,
collectFonts: true,
+ packFn: rrweb.pack
});
};
const replay = () => {
stopFn.value();
showReplay.value = true;
new rrwebPlayer({
// 可以自定義 DOM 元素
target: replayer.value,
// 配置項
props: {
// 傳入events
events: events.value,
+ unpackFn: rrweb.unpack,
},
});
};