引子
移動(dòng)看家運(yùn)營(yíng)頁面,投放在和家親/一級(jí)掌廳/微信/等渠道。以前接入的是和家親的打點(diǎn)方案,現(xiàn)在需要增加安防的打點(diǎn)方案,重點(diǎn)記錄指標(biāo):用戶停留時(shí)長(zhǎng)。
初始思路
停留時(shí)長(zhǎng)的打點(diǎn)方案思路非常清晰:
第一步:在用戶進(jìn)入頁面時(shí)記錄時(shí)間點(diǎn)(在頁面加載后記錄)
第二步:在用戶離開頁面時(shí)記錄時(shí)間點(diǎn) (在頁面即將卸載時(shí)記錄)
第三步:用時(shí)間點(diǎn)計(jì)算時(shí)長(zhǎng),并將信息通過打點(diǎn)接口發(fā)送給平臺(tái)進(jìn)行記錄
項(xiàng)目實(shí)戰(zhàn)
離開頁面事件:onbeforeunload
window.onbeforeunload = funcRef
當(dāng)瀏覽器窗口關(guān)閉或者刷新時(shí),會(huì)觸發(fā)beforeunload事件。當(dāng)前頁面不會(huì)直接關(guān)閉,可以點(diǎn)擊確定按鈕關(guān)閉或刷新,也可以取消關(guān)閉或刷新。
代碼示例:
window.addEventListener('beforeunload', (event) => {
// Cancel the event as stated by the standard.
event.preventDefault();
// Chrome requires returnValue to be set.
event.returnValue = '';
});
注意點(diǎn):
從2011年5月25日起, HTML5 規(guī)范 聲明:在該事件的處理函數(shù)中調(diào)用下列彈窗相關(guān)的方法時(shí),可以忽略不執(zhí)行,window.showModalDialog(), window.alert(), window.confirm() window.prompt().(無法用alert來確認(rèn)是否進(jìn)去了該事件)
需要指出的是,許多瀏覽器會(huì)忽略該事件并自動(dòng)關(guān)閉頁面無需用戶的確認(rèn)。火狐瀏覽器在配置頁面about:config設(shè)有一個(gè)dom.disable_beforeunload的開關(guān)變量用于開啟這個(gè)功能。
瀏覽器兼容性:
移動(dòng)端兼容性很差,safari ,webview中都進(jìn)入不到該事件。
移動(dòng)端離開頁面事件:onpagehide
window.onpagehide = event => {//...}
不要寫在組件里面的unmount事件中,因?yàn)閷?shí)際的使用情況不止是在組件內(nèi)部進(jìn)行頁面跳轉(zhuǎn),還有可能跳轉(zhuǎn)到外部項(xiàng)目鏈接,或者直接關(guān)閉頁面。
移動(dòng)端調(diào)試
怎樣在移動(dòng)端進(jìn)行調(diào)試,確定用戶離開頁面的時(shí)候進(jìn)入到了對(duì)應(yīng)的事件呢?
我的方法是,在事件中設(shè)置對(duì)應(yīng)的localstorage,離開頁面后再次進(jìn)去,看下是否設(shè)置成功即可。
遇到的問題
請(qǐng)求會(huì)丟失
在PC端Chorme瀏覽器調(diào)試發(fā)現(xiàn):頁面跳轉(zhuǎn)時(shí)打點(diǎn)請(qǐng)求狀態(tài)時(shí)canceled,未發(fā)送成功,關(guān)閉瀏覽器時(shí)打點(diǎn)請(qǐng)求發(fā)送成功。
在和家親webview中,ios端打點(diǎn)未發(fā)送成功,但是平臺(tái)有收到零星的時(shí)長(zhǎng)打點(diǎn),說明在某些機(jī)型打點(diǎn)請(qǐng)求發(fā)送成功了。但是這種方法不能保證請(qǐng)求一定被發(fā)出了,繼續(xù)尋找解決方案。
解決方案
服務(wù)器端配置ignore_user_abort防止接口cancel —— 需要服務(wù)端進(jìn)行配置,暫不使用。
-
將打點(diǎn)請(qǐng)求改成同步請(qǐng)求,缺點(diǎn)是由于請(qǐng)求是同步的,在請(qǐng)求時(shí)間內(nèi)會(huì)阻塞頁面關(guān)閉或跳轉(zhuǎn)。
方法:
- ajax調(diào)用設(shè)置async:false
- xhr 調(diào)用設(shè)置
var request = new XMLHttpRequest(); request.open('GET', 'http://www.mozilla.org/', false);
結(jié)果:均無請(qǐng)求發(fā)送記錄,查閱資料,因?yàn)檫@種操作會(huì)阻塞頁面,部分高級(jí)瀏覽器已經(jīng)不在支持在頁面卸載時(shí)間中調(diào)用同步請(qǐng)求。
卸載事件中設(shè)置圖片src為打點(diǎn)地址。缺點(diǎn)是由于請(qǐng)求是同步的,在請(qǐng)求時(shí)間內(nèi)會(huì)阻塞頁面關(guān)閉或跳轉(zhuǎn)。
在頁面卸載事件中將參數(shù)通過bridge傳給native,讓native去和平臺(tái)通信。缺點(diǎn):只能解決在native中的頁面打點(diǎn)事件,無法同步解決其他場(chǎng)景下的打點(diǎn)遺漏問題。
- 終極解決方案
sendBeacon
navigator.sendBeacon() 方法可用于通過HTTP將少量數(shù)據(jù)異步傳輸?shù)絎eb服務(wù)器。
這個(gè)方法主要用于滿足統(tǒng)計(jì)和診斷代碼的需要,這些代碼通常嘗試在卸載(unload)文檔之前向web服務(wù)器發(fā)送數(shù)據(jù)。過早的發(fā)送數(shù)據(jù)可能導(dǎo)致錯(cuò)過收集數(shù)據(jù)的機(jī)會(huì)。然而,對(duì)于開發(fā)者來說保證在文檔卸載期間發(fā)送數(shù)據(jù)一直是一個(gè)困難。因?yàn)橛脩舸硗ǔ?huì)忽略在 unload (en-US) 事件處理器中產(chǎn)生的異步 XMLHttpRequest。
為了解決這個(gè)問題, 統(tǒng)計(jì)和診斷代碼通常要在 unload 或者 beforeunload (en-US) 事件處理器中發(fā)起一個(gè)同步 XMLHttpRequest 來發(fā)送數(shù)據(jù)。同步的 XMLHttpRequest 迫使用戶代理延遲卸載文檔,并使得下一個(gè)導(dǎo)航出現(xiàn)的更晚。下一個(gè)頁面對(duì)于這種較差的載入表現(xiàn)無能為力。
有一些技術(shù)被用來保證數(shù)據(jù)的發(fā)送。其中一種是通過在卸載事件處理器中創(chuàng)建一個(gè)圖片元素并設(shè)置它的 src 屬性的方法來延遲卸載以保證數(shù)據(jù)的發(fā)送。因?yàn)榻^大多數(shù)用戶代理會(huì)延遲卸載以保證圖片的載入,所以數(shù)據(jù)可以在卸載事件中發(fā)送。另一種技術(shù)是通過創(chuàng)建一個(gè)幾秒鐘的 no-op 循環(huán)來延遲卸載并向服務(wù)器發(fā)送數(shù)據(jù)。
這些技術(shù)不僅編碼模式不好,其中的一些甚至并不可靠而且會(huì)導(dǎo)致非常差的頁面載入性能。
官方示例
window.addEventListener('unload', logData, false);
function logData() {
navigator.sendBeacon("/log", analyticsData);
}
data 參數(shù)是將要發(fā)送的 ArrayBufferView 或 Blob, DOMString 或者 FormData 類型的數(shù)據(jù)。
缺點(diǎn)是不能設(shè)置復(fù)雜的請(qǐng)求頭,數(shù)據(jù)格式也有要求。
