三分鐘,教你3種前端埋點方式!

埋點方式
在聊如何進行埋點前,我們先介紹下什么是埋點?

所謂'埋點'是數(shù)據(jù)采集領域(尤其是用戶行為數(shù)據(jù)采集領域)的術語,指的是針對特定用戶行為或事件進行捕獲、處理和發(fā)送的相關技術及其實施過程。. 比如用戶某個icon點擊次數(shù)、觀看某個視頻的時長等等。
從數(shù)據(jù)產(chǎn)品經(jīng)理視角,聊聊埋點的意義 | 人人都是產(chǎn)品經(jīng)理 (woshipm.com)[1]

基于此我們可以知道埋點是實際上是對特定事件或者行為的數(shù)據(jù)監(jiān)控和上報,常見的埋點上報方式有ajax,img,navigator.sendBeacon下面介紹下這三種埋點上報方式

基于ajax的埋點上報
介紹
因為埋點實際上是對關鍵節(jié)點的數(shù)據(jù)進行上報是和服務端交互的一個過程,所以我們可以和后端約定一個接口通過ajax去進行數(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的對象
缺點
一般而言,埋點域名并不是當前域名,因此請求會存在跨域風險,且如果ajax配置不正確可能會瀏覽器攔截。因此使用ajax這類請求并不是萬全之策。

基于img的埋點上報
上面可以看到如果使用ajax的話,會存在跨域的問題。而且數(shù)據(jù)上報前端主要是負責將數(shù)據(jù)傳遞到后端,并不過分強調(diào)前后端交互。
因此我們可以通過一些支持跨域的標簽去實現(xiàn)數(shù)據(jù)上報功能。

script,link,img就是我們上報的數(shù)據(jù)的最好對象

先說結(jié)論,這里推薦使用img標簽去實現(xiàn)。

script及l(fā)ink的缺陷
因為埋點涉及到請求,因此我們需要保證script和link標簽的src可以正常請求。
如果需要請求script和link,我們需要將標簽掛載到頁面上。

驗證缺陷
不妨驗證下,我們在管理臺中加入以下代碼:

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標簽,未掛載中頁面上,并不會發(fā)起請求

238723bk-1.jpg

書接上文,當我們將這個標簽掛載中頁面上時:

document.body.appendChild(a)
這時發(fā)起了請求

238723bk-2.jpg

結(jié)論
當我們使用script和link進行埋點上報時,需要掛載到頁面上,而反復操作dom會造成頁面性能受影響,而且載入js/css資源還會阻塞頁面渲染,影響用戶體驗,因此對于需要頻繁上報的埋點而言,script和link并不合適。

基于img做埋點上報
通常使用img標簽去做埋點上報,img標簽加載并不需要掛載到頁面上,基于js去new image(),設置其src之后就可以直接請求圖片。

驗證img優(yōu)勢
控制臺去創(chuàng)建一個image標簽,如下:

var img=new Image();
img.src="https://lf3-cdn-tos.bytescm.com/obj/static/xitu_juejin_web/img/MaskGroup.13dfc4f1.png";
可以看到即便未被掛載到頁面上依舊發(fā)起了請求。

238723bk-3.jpg

結(jié)論
因此當我們做埋點上報時,使用img是一個不錯的選擇。

img兼容性好
無需掛載到頁面上,反復操作dom
img的加載不會阻塞html的解析,但img加載后并不渲染,它需要等待Render Tree生成完后才和Render Tree一起渲染出來
注:通常埋點上報會使用gif圖,合法的 GIF 只需要 43 個字節(jié)

基于Navigator.sendBeacon的埋點上報
Navigator.sendBeacon是目前通用的埋點上報方案,Navigator.sendBeacon方法接受兩個參數(shù),第一個參數(shù)是目標服務器的 URL,第二個參數(shù)是所要發(fā)送的數(shù)據(jù)(可選),可以是任意類型(字符串、表單對象、二進制對象等等)。

介紹
navigator.sendBeacon()  方法可用于通過 HTTP POST[2] 將少量數(shù)據(jù) 異步[3] 傳輸?shù)?Web 服務器。

作用
它主要用于將統(tǒng)計數(shù)據(jù)發(fā)送到 Web 服務器,同時避免了用傳統(tǒng)技術(如:`XMLHttpRequest`[4])發(fā)送分析數(shù)據(jù)的一些問題。

補充
sendBeacon 如果成功進入瀏覽器的發(fā)送隊列后,會返回true;如果受到隊列總數(shù)、數(shù)據(jù)大小的限制后,會返回false。返回ture后,只是表示進入了發(fā)送隊列,瀏覽器會盡力保證發(fā)送成功,但是否成功了,不會再有任何返回值。

例子
以掘金為例:

238723bk-4.jpg

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

238723bk-5.jpg

優(yōu)勢
相較于img標簽,使用navigator.sendBeacon會更規(guī)范,數(shù)據(jù)傳輸上可傳輸資源類型會更多。

對于ajax在頁面卸載時上報,ajax有可能沒上報完,頁面就卸載了導致請求中斷,因此ajax處理這種情況時必須作為同步操作.

sendBeacon是異步的,不會影響當前頁到下一個頁面的跳轉(zhuǎn)速度,且不受同域限制。這個方法還是異步發(fā)出請求,但是請求與當前頁面脫離關聯(lián),作為瀏覽器的任務,因此可以保證會把數(shù)據(jù)發(fā)出去,不拖延卸載流程。

總結(jié)
前端埋點上報常使用ajax,img,navigator.sendBeacon。
不推薦使用ajax。
如果考慮兼容性的話,img是不二之選。
目前最合適的方案是navigator.sendBeacon,不僅是異步的,而且不受同域限制,而且作為瀏覽器的任務,因此可以保證會把數(shù)據(jù)發(fā)出去,不影響頁面卸載。

常見埋點行為
點擊觸發(fā)埋點
綁定點擊事件,當點擊目標元素時,觸發(fā)埋點上報。

function clickButton(url, data) {
    navigator.sendBeacon(url, data)
}
頁面停留時間上報埋點
路由文件中,初始化一個startTime,當頁面離開時通過路由守衛(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)聽當前元素是否出現(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);
// 獲取目標節(jié)點
let target = document.getElementById("target");
// 監(jiān)聽目標元素
observer.observe(target);



作者:彩虹修狗


歡迎關注微信公眾號 :前端Q