- 收集一段時間內使用者游標在頁面中的運動情況,包括游標移動、點選等行為
- 統計使用者滾屏行為
- 統計使用者在站點的停留時長
- 收集頁面連結的點選數量等
無論是移動端還是 PC 端,相信很多朋友都遇到了這麼幾個十分讓人頭疼的問題:
- 統計某個連結的點選量,但是這個連結點選後直接跳轉走了
- 統計頁面時長問題,unload 的時候傳送的統計丟失了
- 統計指令碼還沒有初始化,使用者不感興趣已經走人了等
如果我們把這樣的資料交給了產品同學,可能會讓他們對使用者行為產生錯誤的認知,一定程度上影響產品的下一步改善。
傳統解決方案
上面提到的問題,從技術角度可以歸納為兩點:
- 使用者關閉頁面過早,統計指令碼還未載入/初始化完成
- 使用者關閉或者跳出頁面的時候,請求未發出
針對第一點,概率較小,一般的處理方式就是,不要把統計指令碼參合到其他指令碼中,單獨載入,並且放在前頭,讓它優先載入。很多公司的做法是,不讓開發者關心統計指令碼的載入,使用者請求頁面的時候,Nginx 會在 Body 開始標籤位置注入一段指令碼。
對於問題二,處理方案就有很多了。
1. 阻塞式的 Ajax 請求
還記得 XMLHttpRequest::open
方法的第三個引數吧,如果設定為 false 就是同步載入,
1 2 3 4 5 6 |
window.addEventListener('unload', function(event) { var xhr = new XMLHttpRequest(), xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8"); xhr.open('post', '/log', false); // 同步請求 xhr.send(data); }); |
阻塞頁面關閉,當然可以在 readState
為 2 的時候就 abort 請求,因為我們不關心響應的內容,只要請求發出去就行了。
2. 暴力的死迴圈
原理跟上面類似,只不過是使用一個空的死迴圈阻塞頁面關閉,
1 2 3 4 5 |
window.addEventListener('unload', function(event) { send(data); var now = +new Date; while(new Date - now >= 10) {} // 阻塞 10ms }); |
3. 發一個圖片請求阻塞
大部分瀏覽器都會等待圖片的載入,趁這個機會把統計資料傳送出去
1 2 3 4 |
window.addEventListener('unload', function(event) { send(data); (new Image).src = 'http://example.com/s.gif'; }); |
以上提到的幾個方案都是一個原理,讓瀏覽器繼續保持阻塞狀態,等資料傳送出去後再跳轉,這裡存在的問題是:
- 少量瀏覽器下可能不奏效
- 等待一會兒再跳轉,使用者體驗上打了折扣,尤其是移動端上
是否有更好的方案解決這個問題呢,前端同學秉著「小強精神」也提出了兩個可實踐的方案。
優化方案
不就是埋點統計資料嘛,非得在當前頁面傳送出去?優化方案的思路具有一定的跳躍性,我們考慮將資料在下跳頁中傳送,那麼問題就轉換為,如何將資料傳遞給下跳頁?
對於連結點選量的統計,我們可以將連結資訊通過 url 傳遞給下跳頁,傳遞思路如下:
1. url 傳參
通過陣列標識一個連結的位置資訊,如 [站點id,頁面id,模組id,連結index]
,通過四個引數可以惟一標識連結位置屬性,使用 URL param 引數將陣列資料傳遞給下跳頁,等待由下跳頁將資料傳送出去。
這裡存在的問題是,下跳頁中必須部署同樣的統計指令碼,但對一個系統來說,這是很容易做到的。我們也不會在自己的網頁上放其他網站的連結吧,所以整個資料的統計都在一個閉環內。
2. 通過 window.name
傳遞資料
window.name
是瀏覽器給我們開放的一個介面,設定該屬性的值後,即便頁面發生了跳轉,這個值依然不會變化,並且可以跨域使用。
這裡存在的問題是,該屬性可能被開發者用於其他途徑。我們可以限制開發者直接使用 window.name
,封裝介面,通過介面呼叫,如 aralejs 提供的 nameStorage,
1 2 3 |
nameStorage.setItem(key, value); nameStorage.getItem(key); nameStorage.removeItem(key); |
儲存形式為:
1 2 3 4 5 6 7 |
scheme nameStorage datas | | ------------ ------------------------ nameStorage:origin-name?key1=value1&key2=value2 ----------- | window origin name |
以上雖然基本解決了資料丟失和體驗差的問題,但是這也很大程度依賴於開發者的程式設計習慣,如不能隨便玩耍 window.name
;也對系統有一定的要求,必須在所有頁面上部署同樣的埋點指令碼。
這件事情應該交給瀏覽器來解決
上面提到的各種方案,不乏黑科技,然而存在的問題還是一大堆,如果團隊的開發者執行力不夠,中途容易出現各種麻煩。所以真正能夠解決這個問題的,必然還是瀏覽器本身!
為什麼不能給使用者提供這樣一個 API,即使頁面跳轉了,也能夠將上個頁面的請求發出去呢?慶幸的是,W3C 工作組也想到了這個問題,提出了 Beacon API
的 草案。
Beacon API
允許開發者傳送少量錯誤分析和上報的資訊,它的特點很明顯:
- 在空閒的時候非同步傳送統計,不影響頁面諸如 JS、CSS Animation 等執行
- 即使頁面在 unload 狀態下,也會非同步傳送統計,不影響頁面過渡/跳轉到下跳頁
- 能夠被客戶端優化傳送,尤其在 Mobile 環境下,可以將 Beacon 請求合併到其他請求上,一同處理
sendBeacon
函式掛在在 navigator 上,在 unload 之前,這個函式一定是被初始化了的。其使用方式為:
1 2 3 |
window.addEventListener('unload', function(event) { navigator.sendBeacon('/collector', data); }); |
navigator.sendBeacon(url, data);
,第一個引數為資料上報的地址,第二個引數為要傳送的資料,支援的資料格式有:ArrayBufferView, Blob, DOMString, 和 FormData。
Beacon
的還有一個非常實用的移動端使用場景,當使用者從瀏覽器切換到其他 app 介面或者 Home 屏的時候,部分瀏覽器預設會停止頁面指令碼的執行,如果在這個時候使用了 unload 時間,可能會讓你失望,因為 unload 事件並不會觸發,此時,Beacon
就派上用途了,它是不會受影響的。
最後
本文是對頁面打點丟失問題的簡單探討,列舉了我們通常會用到的一些解決方案,可能不是很完善,如果你有更好的建議,可以提出來。
很多問題,我們絞盡腦汁,可能很少會考慮,這個問題是不是應該有我們來解決,或者說這個問題交給誰處理是最恰當的。本文的探討可以看到,瀏覽器本身才是最好的問題解決方,當網站流量變大之後,上面提到的丟失問題就更加明顯,這也迫使瀏覽器本身做了改善,自然也在情理之中。