三分鐘,教你3種前端埋點方式!
作者:彩虹修狗
https://juejin.cn/post/7224132741997281338
前言
只有了解用戶,我們才能服務(wù)好用戶,而最接近用戶的我們,自然要承擔(dān)起更多的責(zé)任。
那么在一個企業(yè)中,我們要如何去了解用戶呢?
最直接有效的方式就是了解用戶的行為,了解用戶在網(wǎng)站中做了什么,呆了多久。
而如何去實現(xiàn)這一操作,這就涉及到我們前端的埋點了。
埋點方式
在聊如何進(jìn)行埋點前,我們先介紹下什么是埋點?
所謂'埋點'是數(shù)據(jù)采集領(lǐng)域(尤其是用戶行為數(shù)據(jù)采集領(lǐng)域)的術(shù)語,指的是針對特定用戶行為或事件進(jìn)行捕獲、處理和發(fā)送的相關(guān)技術(shù)及其實施過程。. 比如用戶某個icon點擊次數(shù)、觀看某個視頻的時長等等。
我們可以知道埋點是實際上是對特定事件或者行為的數(shù)據(jù)監(jiān)控和上報,常見的埋點上報方式有ajax,img,navigator.sendBeacon下面介紹下這三種埋點上報方式
基于ajax的埋點上報
介紹
因為埋點實際上是對關(guān)鍵節(jié)點的數(shù)據(jù)進(jìn)行上報是和服務(wù)端交互的一個過程,所以我們可以和后端約定一個接口通過ajax去進(jìn)行數(shù)據(jù)上報。
代碼實現(xiàn)
我們可以封裝一個方法,代碼如下:
function?buryingPointAjax(data)?{
??return?new?Promise((resolve,?reject)?=>?{
????//?創(chuàng)建ajax請求
????const?xhr?=?new?XMLHttpRequest();
????//?定義請求接口
????xhr.open("post",?'/buryingPoint',?true);
????//?發(fā)送數(shù)據(jù)
????xhr.send(data);
??});
}
使用時,直接調(diào)用即可
let?info?=?{}
buryingPointAjax(info)?//?這樣就成功上報了info的對象
缺點
一般而言,埋點域名并不是當(dāng)前域名,因此請求會存在跨域風(fēng)險,且如果ajax配置不正確可能會瀏覽器攔截。因此使用ajax這類請求并不是萬全之策。
基于img的埋點上報
上面可以看到如果使用ajax的話,會存在跨域的問題。而且數(shù)據(jù)上報前端主要是負(fù)責(zé)將數(shù)據(jù)傳遞到后端,并不過分強調(diào)前后端交互。
因此我們可以通過一些支持跨域的標(biāo)簽去實現(xiàn)數(shù)據(jù)上報功能。
script,link,img就是我們上報的數(shù)據(jù)的最好對象
先說結(jié)論,這里推薦使用img標(biāo)簽去實現(xiàn)。
script及l(fā)ink的缺陷
因為埋點涉及到請求,因此我們需要保證script和link標(biāo)簽的src可以正常請求。
如果需要請求script和link,我們需要將標(biāo)簽掛載到頁面上。
驗證缺陷
不妨驗證下,我們在管理臺中加入以下代碼:
let?a?=?document.createElement('script')
a.src?=?'https://lf-headquarters-speed.yhgfb-cn-static.com/obj/rc-client-security/web/stable/1.0.0.28/bdms.js'
創(chuàng)建一個script標(biāo)簽,未掛載中頁面上,并不會發(fā)起請求

書接上文,當(dāng)我們將這個標(biāo)簽掛載中頁面上時:
document.body.appendChild(a)
這時發(fā)起了請求

結(jié)論
當(dāng)我們使用script和link進(jìn)行埋點上報時,需要掛載到頁面上,而反復(fù)操作dom會造成頁面性能受影響,而且載入js/css資源還會阻塞頁面渲染,影響用戶體驗,因此對于需要頻繁上報的埋點而言,script和link并不合適。
基于img做埋點上報
通常使用img標(biāo)簽去做埋點上報,img標(biāo)簽加載并不需要掛載到頁面上,基于js去new image(),設(shè)置其src之后就可以直接請求圖片。
驗證img優(yōu)勢
控制臺去創(chuàng)建一個image標(biāo)簽,如下:
var?img=new?Image();
img.src="https://lf3-cdn-tos.bytescm.com/obj/static/xitu_juejin_web/img/MaskGroup.13dfc4f1.png";
可以看到即便未被掛載到頁面上依舊發(fā)起了請求。

結(jié)論
因此當(dāng)我們做埋點上報時,使用img是一個不錯的選擇。
img兼容性好 無需掛載到頁面上,反復(fù)操作dom img的加載不會阻塞html的解析,但img加載后并不渲染,它需要等待Render Tree生成完后才和Render Tree一起渲染出來
注:通常埋點上報會使用gif圖,合法的 GIF 只需要 43 個字節(jié)
基于Navigator.sendBeacon的埋點上報
Navigator.sendBeacon是目前通用的埋點上報方案,Navigator.sendBeacon方法接受兩個參數(shù),第一個參數(shù)是目標(biāo)服務(wù)器的 URL,第二個參數(shù)是所要發(fā)送的數(shù)據(jù)(可選),可以是任意類型(字符串、表單對象、二進(jìn)制對象等等)。
介紹
navigator.sendBeacon() 方法可用于通過 HTTP POST[1] 將少量數(shù)據(jù) 異步[2] 傳輸?shù)?Web 服務(wù)器。
作用
它主要用于將統(tǒng)計數(shù)據(jù)發(fā)送到 Web 服務(wù)器,同時避免了用傳統(tǒng)技術(shù)(如:XMLHttpRequest[3])發(fā)送分析數(shù)據(jù)的一些問題。
補充
sendBeacon 如果成功進(jìn)入瀏覽器的發(fā)送隊列后,會返回true;如果受到隊列總數(shù)、數(shù)據(jù)大小的限制后,會返回false。返回ture后,只是表示進(jìn)入了發(fā)送隊列,瀏覽器會盡力保證發(fā)送成功,但是否成功了,不會再有任何返回值。
例子
以掘金為例:

這里發(fā)了一個post請求,將小量的數(shù)據(jù)發(fā)到服務(wù)端,用于統(tǒng)計數(shù)據(jù)

優(yōu)勢
相較于img標(biāo)簽,使用navigator.sendBeacon會更規(guī)范,數(shù)據(jù)傳輸上可傳輸資源類型會更多。
對于ajax在頁面卸載時上報,ajax有可能沒上報完,頁面就卸載了導(dǎo)致請求中斷,因此ajax處理這種情況時必須作為同步操作.
sendBeacon是異步的,不會影響當(dāng)前頁到下一個頁面的跳轉(zhuǎn)速度,且不受同域限制。這個方法還是異步發(fā)出請求,但是請求與當(dāng)前頁面脫離關(guān)聯(lián),作為瀏覽器的任務(wù),因此可以保證會把數(shù)據(jù)發(fā)出去,不拖延卸載流程。
總結(jié)
前端埋點上報常使用ajax,img,navigator.sendBeacon。
不推薦使用ajax。
如果考慮兼容性的話,img是不二之選。
目前最合適的方案是navigator.sendBeacon,不僅是異步的,而且不受同域限制,而且作為瀏覽器的任務(wù),因此可以保證會把數(shù)據(jù)發(fā)出去,不影響頁面卸載。
常見埋點行為
點擊觸發(fā)埋點
綁定點擊事件,當(dāng)點擊目標(biāo)元素時,觸發(fā)埋點上報。
function?clickButton(url,?data)?{
????navigator.sendBeacon(url,?data)
}
頁面停留時間上報埋點
路由文件中,初始化一個startTime,當(dāng)頁面離開時通過路由守衛(wèi)計算停留時間。
let?url?=?''//?上報地址
let?startTime?=?Date.now()
let?currentTime?=?''
router.beforeEach((to,?from,?next)?=>?{?
?????if?(to)?{
?????????currentTime?=?Date.now()
?????????stayTime?=?parseInt(currentTime?-?startTime)
?????????navigator.sendBeacon(url,?{time:?stayTime})
?????????startTime?=?Date.now()
?????}
?})
錯誤監(jiān)聽埋點
通過監(jiān)聽函數(shù)去接收錯誤信息。
vue錯誤捕獲
app.config.errorHandler?=?(err)?=>?{?
????navigator.sendBeacon(url,?{error:?error.message,?text:?'vue運行異常'?})
}
JS異常與靜態(tài)資源加載異常
window.addEventListener('error',?(error)?=>?{?
????if?(error.message)?{?
????????navigator.sendBeacon(url,?{error:?error.message,?text:?'js執(zhí)行異常'?})
????}?else?{?
????????navigator.sendBeacon(url,?{error:?error.filename,?text:?'資源加載異常'?})
????}?
},?true)
請求錯誤捕獲
axios.interceptors.response.use(
??(response)?=>?{
????if?(response.code?==?200)?{
??????return?Promise.resolve(response);
????}?else?{
??????return?Promise.reject(response);
????}
??},
??(error)?=>?{
????//?返回錯誤邏輯
????navigator.sendBeacon(url,?{error:?error,?text:?'請求錯誤異常'?})
??}
);
內(nèi)容可見埋點
通過交叉觀察器去監(jiān)聽當(dāng)前元素是否出現(xiàn)在頁面
//?可見性發(fā)生變化后的回調(diào)?
function?callback(data)?{?
????navigator.sendBeacon(url,?{?target:?data[0].target,?text:?'內(nèi)容可見'?})?
}?
//?交叉觀察器配置項?
let?options?=?{};?
//?生成交叉觀察器?
const?observer?=?new?IntersectionObserver(callback);?
//?獲取目標(biāo)節(jié)點?
let?target?=?document.getElementById("target");?
//?監(jiān)聽目標(biāo)元素?
observer.observe(target);
參考資料
HTTP POST: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/POST
[2]異步: https://developer.mozilla.org/zh-CN/docs/Glossary/Asynchronous
[3]XMLHttpRequest: https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
