三分鐘,教你3種前端埋點(diǎn)方式!
埋點(diǎn)方式
在聊如何進(jìn)行埋點(diǎn)前,我們先介紹下什么是埋點(diǎn)?
所謂'埋點(diǎn)'是數(shù)據(jù)采集領(lǐng)域(尤其是用戶行為數(shù)據(jù)采集領(lǐng)域)的術(shù)語,指的是針對(duì)特定用戶行為或事件進(jìn)行捕獲、處理和發(fā)送的相關(guān)技術(shù)及其實(shí)施過程。. 比如用戶某個(gè)icon點(diǎn)擊次數(shù)、觀看某個(gè)視頻的時(shí)長等等。
從數(shù)據(jù)產(chǎn)品經(jīng)理視角,聊聊埋點(diǎn)的意義 | 人人都是產(chǎn)品經(jīng)理 (woshipm.com)[1]
基于此我們可以知道埋點(diǎn)是實(shí)際上是對(duì)特定事件或者行為的數(shù)據(jù)監(jiān)控和上報(bào),常見的埋點(diǎn)上報(bào)方式有ajax,img,navigator.sendBeacon下面介紹下這三種埋點(diǎn)上報(bào)方式
基于ajax的埋點(diǎn)上報(bào)
介紹
因?yàn)槁顸c(diǎn)實(shí)際上是對(duì)關(guān)鍵節(jié)點(diǎn)的數(shù)據(jù)進(jìn)行上報(bào)是和服務(wù)端交互的一個(gè)過程,所以我們可以和后端約定一個(gè)接口通過ajax去進(jìn)行數(shù)據(jù)上報(bào)。
代碼實(shí)現(xiàn)
我們可以封裝一個(gè)方法,代碼如下:
function buryingPointAjax(data) {
return new Promise((resolve, reject) => {
// 創(chuàng)建ajax請(qǐng)求
const xhr = new XMLHttpRequest();
// 定義請(qǐng)求接口
xhr.open("post", '/buryingPoint', true);
// 發(fā)送數(shù)據(jù)
xhr.send(data);
});
}
使用時(shí),直接調(diào)用即可
let info = {}
buryingPointAjax(info) // 這樣就成功上報(bào)了info的對(duì)象
缺點(diǎn)
一般而言,埋點(diǎn)域名并不是當(dāng)前域名,因此請(qǐng)求會(huì)存在跨域風(fēng)險(xiǎn),且如果ajax配置不正確可能會(huì)瀏覽器攔截。因此使用ajax這類請(qǐng)求并不是萬全之策。
基于img的埋點(diǎn)上報(bào)
上面可以看到如果使用ajax的話,會(huì)存在跨域的問題。而且數(shù)據(jù)上報(bào)前端主要是負(fù)責(zé)將數(shù)據(jù)傳遞到后端,并不過分強(qiáng)調(diào)前后端交互。
因此我們可以通過一些支持跨域的標(biāo)簽去實(shí)現(xiàn)數(shù)據(jù)上報(bào)功能。
script,link,img就是我們上報(bào)的數(shù)據(jù)的最好對(duì)象
先說結(jié)論,這里推薦使用img標(biāo)簽去實(shí)現(xiàn)。
script及l(fā)ink的缺陷
因?yàn)槁顸c(diǎn)涉及到請(qǐng)求,因此我們需要保證script和link標(biāo)簽的src可以正常請(qǐng)求。
如果需要請(qǐng)求script和link,我們需要將標(biāo)簽掛載到頁面上。
驗(yàn)證缺陷
不妨驗(yàn)證下,我們?cè)诠芾砼_(tái)中加入以下代碼:
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)建一個(gè)script標(biāo)簽,未掛載中頁面上,并不會(huì)發(fā)起請(qǐng)求
書接上文,當(dāng)我們將這個(gè)標(biāo)簽掛載中頁面上時(shí):
document.body.appendChild(a)
這時(shí)發(fā)起了請(qǐng)求
結(jié)論
當(dāng)我們使用script和link進(jìn)行埋點(diǎn)上報(bào)時(shí),需要掛載到頁面上,而反復(fù)操作dom會(huì)造成頁面性能受影響,而且載入js/css資源還會(huì)阻塞頁面渲染,影響用戶體驗(yàn),因此對(duì)于需要頻繁上報(bào)的埋點(diǎn)而言,script和link并不合適。
基于img做埋點(diǎn)上報(bào)
通常使用img標(biāo)簽去做埋點(diǎn)上報(bào),img標(biāo)簽加載并不需要掛載到頁面上,基于js去new image(),設(shè)置其src之后就可以直接請(qǐng)求圖片。
驗(yàn)證img優(yōu)勢(shì)
控制臺(tái)去創(chuàng)建一個(gè)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ā)起了請(qǐng)求。
結(jié)論
因此當(dāng)我們做埋點(diǎn)上報(bào)時(shí),使用img是一個(gè)不錯(cuò)的選擇。
img兼容性好
無需掛載到頁面上,反復(fù)操作dom
img的加載不會(huì)阻塞html的解析,但img加載后并不渲染,它需要等待Render Tree生成完后才和Render Tree一起渲染出來
注:通常埋點(diǎn)上報(bào)會(huì)使用gif圖,合法的 GIF 只需要 43 個(gè)字節(jié)
基于Navigator.sendBeacon的埋點(diǎn)上報(bào)
Navigator.sendBeacon是目前通用的埋點(diǎn)上報(bào)方案,Navigator.sendBeacon方法接受兩個(gè)參數(shù),第一個(gè)參數(shù)是目標(biāo)服務(wù)器的 URL,第二個(gè)參數(shù)是所要發(fā)送的數(shù)據(jù)(可選),可以是任意類型(字符串、表單對(duì)象、二進(jìn)制對(duì)象等等)。
介紹
navigator.sendBeacon() 方法可用于通過 HTTP POST[2] 將少量數(shù)據(jù) 異步[3] 傳輸?shù)?Web 服務(wù)器。
作用
它主要用于將統(tǒng)計(jì)數(shù)據(jù)發(fā)送到 Web 服務(wù)器,同時(shí)避免了用傳統(tǒng)技術(shù)(如:`XMLHttpRequest`[4])發(fā)送分析數(shù)據(jù)的一些問題。
補(bǔ)充
sendBeacon 如果成功進(jìn)入瀏覽器的發(fā)送隊(duì)列后,會(huì)返回true;如果受到隊(duì)列總數(shù)、數(shù)據(jù)大小的限制后,會(huì)返回false。返回ture后,只是表示進(jìn)入了發(fā)送隊(duì)列,瀏覽器會(huì)盡力保證發(fā)送成功,但是否成功了,不會(huì)再有任何返回值。
例子
以掘金為例:
這里發(fā)了一個(gè)post請(qǐng)求,將小量的數(shù)據(jù)發(fā)到服務(wù)端,用于統(tǒng)計(jì)數(shù)據(jù)
優(yōu)勢(shì)
相較于img標(biāo)簽,使用navigator.sendBeacon會(huì)更規(guī)范,數(shù)據(jù)傳輸上可傳輸資源類型會(huì)更多。
對(duì)于ajax在頁面卸載時(shí)上報(bào),ajax有可能沒上報(bào)完,頁面就卸載了導(dǎo)致請(qǐng)求中斷,因此ajax處理這種情況時(shí)必須作為同步操作.
sendBeacon是異步的,不會(huì)影響當(dāng)前頁到下一個(gè)頁面的跳轉(zhuǎn)速度,且不受同域限制。這個(gè)方法還是異步發(fā)出請(qǐng)求,但是請(qǐng)求與當(dāng)前頁面脫離關(guān)聯(lián),作為瀏覽器的任務(wù),因此可以保證會(huì)把數(shù)據(jù)發(fā)出去,不拖延卸載流程。
總結(jié)
前端埋點(diǎn)上報(bào)常使用ajax,img,navigator.sendBeacon。
不推薦使用ajax。
如果考慮兼容性的話,img是不二之選。
目前最合適的方案是navigator.sendBeacon,不僅是異步的,而且不受同域限制,而且作為瀏覽器的任務(wù),因此可以保證會(huì)把數(shù)據(jù)發(fā)出去,不影響頁面卸載。
常見埋點(diǎn)行為
點(diǎn)擊觸發(fā)埋點(diǎn)
綁定點(diǎn)擊事件,當(dāng)點(diǎn)擊目標(biāo)元素時(shí),觸發(fā)埋點(diǎn)上報(bào)。
function clickButton(url, data) {
navigator.sendBeacon(url, data)
}
頁面停留時(shí)間上報(bào)埋點(diǎn)
路由文件中,初始化一個(gè)startTime,當(dāng)頁面離開時(shí)通過路由守衛(wèi)計(jì)算停留時(shí)間。
let url = ''// 上報(bào)地址
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()
}
})
錯(cuò)誤監(jiān)聽埋點(diǎn)
通過監(jiān)聽函數(shù)去接收錯(cuò)誤信息。
vue錯(cuò)誤捕獲
app.config.errorHandler = (err) => {
navigator.sendBeacon(url, {error: error.message, text: 'vue運(yùn)行異常' })
}
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)
請(qǐng)求錯(cuò)誤捕獲
axios.interceptors.response.use(
(response) => {
if (response.code == 200) {
return Promise.resolve(response);
} else {
return Promise.reject(response);
}
},
(error) => {
// 返回錯(cuò)誤邏輯
navigator.sendBeacon(url, {error: error, text: '請(qǐng)求錯(cuò)誤異常' })
}
);
內(nèi)容可見埋點(diǎn)
通過交叉觀察器去監(jiān)聽當(dāng)前元素是否出現(xiàn)在頁面
// 可見性發(fā)生變化后的回調(diào)
function callback(data) {
navigator.sendBeacon(url, { target: data[0].target, text: '內(nèi)容可見' })
}
// 交叉觀察器配置項(xiàng)
let options = {};
// 生成交叉觀察器
const observer = new IntersectionObserver(callback);
// 獲取目標(biāo)節(jié)點(diǎn)
let target = document.getElementById("target");
// 監(jiān)聽目標(biāo)元素
observer.observe(target);
作者:彩虹修狗
歡迎關(guān)注微信公眾號(hào) :前端Q