前言
為什麼要做這個監測使用者停留的呢?原因很簡單,如果我們要分析這個頁面對我們的產品有沒有價格,那麼使用者瀏覽的時長是一個很關鍵的點,如果每個使用者平均每天在這個頁面停留兩個小時以上,那麼我們會覺得這個頁面的價值很高;如果一個頁面一個月也沒幾個使用者去瀏覽,那我們就會有疑問,這個頁面對我們的產品還有價值嗎?我們的產品後續還要保留它的?這些需求都是可以讓我們考慮是否要去獲取使用者停留時長這個功能的。
針對哪些應用?
多頁面應用
單頁面應用(本文以vue為例子)
如何去獲取使用者停留的時長?
在監測功能的時候,我們首要考慮的就是,我們的監測程式碼不能影響我們現在的業務程式碼,和以後的業務程式碼。
多頁面應用
在多頁面應用,要獲取使用者的停留時間還是挺簡單的,看看下面幾個Api
onload[1]
(頁面載入完後)
onbeforeunload[2]
(頁面解除安裝前,也就是點選叉的時候)
onpageshow[3]
(頁面顯示的時候)
onpagehide[4]
(頁面隱藏的時候)
經過自己的一點小測試,發現無論在關閉的時候(也就是點選叉),還是隱藏的時候(也就是點選左右箭頭)都會觸發
onpagehide
;也無論是在首次載入,還是重新整理的時候,都會觸發
onpageshow
,但是重新整理的時候會先觸發
onpagehide
,在觸發
onpageshow
。其他兩個API也是類似,最後,個人選擇了用
onpageshow
和
onpagehide
這個兩個API獲取使用者停留的時長,你也可以用其他兩個API做。只要在
onpageshow
初始時間值,在
onpagehide
的時間求出差值,然後上傳到後臺就行。
let stopTimewindow。onpageshow = ()=>{ stopTime = new Date()。getTime()}window。onpagehide = ()=>{ stopTime = new Date()。getTime() - stopTime let record = localStorage。getItem(‘data’) let data = record && JSON。parse(record) || [] localStorage。setItem(‘data’,JSON。stringify([。。。data,{user:new Date()。getTime(),path:window。location。href,stopTime}]))}
完整程式碼就那麼點,這裡我只是測試,就把模擬的使用者id,真實的路徑,真實的停留時長儲存了在localStorage,在專案中可以傳到後臺,然後透過分析,再視覺化展示出來。
上面效果圖(錄得GIF就那AV畫質,沒錢開VIP,將就看吧,有點AV感覺也好,哈哈),第一次點選的是
叉
,點選叉那也可以理解為離開了頁面,第二次
a連結跳轉
,第三次
重新整理
,重新整理也可以理解為一次離開吧,一共存了三條資料,有興趣自己試試更好理解。
單頁面應用
單頁面應用可能,會複雜一點,但是也複雜不到哪裡去。
單頁面應用的路由跳轉,都是基於
H5的History API(browserHistory)
和
Hash(hashHistory)
實現的。
browserHistory
單頁面的
browserHistory路由
是基於H5的History API實現的,我們只要監聽
popstate
就可以知道,點選前進後退按鈕改變的url變化,url發生變化,我們就能統計使用者在該頁面待了多長時間,程式碼如下
let timeStrwindow。addEventListener(‘onload’,(e)=>{ timeStr = new Date()。getTime()})window。addEventListener(‘popstate’,()=>{ let t = new Date()。getTime() - timeStr timeStr = new Date()。getTime() console。log(‘待了時長:’+ t)})
但是,
pushState和replaceState(也就是,點選router-view,$router。push,$router。replace,window。history。pushState,window。history。replaceState不會觸發,可以自行試試)
不會觸發
popstate
,那我們就統計不了使用者待在該頁面的時長的了;可是解決方法還是有的,只需要重寫
pushState和replaceState
,然後監聽兩個自定義事件就行,看下面程式碼
// 對原函式做一個拓展let rewriteHis = function(type){ let origin = window。history[type] // 先將原函式存放起來 return function(){ // 當window。history[type]函式被執行時,這個函式就會被執行 let rs = origin。apply(this, arguments) // 執行原函式 let e = new Event(type。toLocaleLowerCase()) // 定義一個自定義事件 e。arguments = arguments // 把預設引數,繫結到自定義事件上,new Event返回的結果,自身上是沒有arguments的 window。dispatchEvent(e) // 觸發自定義事件,把載荷傳給自定義事件 return rs }}window。history。pushState = rewriteHis(‘pushState’) // 覆蓋原來的pushState方法window。history。replaceState = rewriteHis(‘replaceState’) // 覆蓋原來的replaceState方法// 監聽自定義事件, pushstate事件是在rewriteHis時註冊的,不是原生事件// 當點選router-link 或者 window。history。pushState 或者 this。$router。push 時都會被該事件監聽到window。addEventListener(‘pushstate’,()=>{})// 監聽自定義事件, replacestate事件是在rewriteHis時註冊的,不是原生事件// 當點選window。history。replaceState 或者 this。$router。replace 時都會被該事件監聽到window。addEventListener(‘replacestate’,()=>{})
rewriteHis函式
,這個函式主要是對原函式做了一個拓展,上面程式碼的註釋應該說了很清楚了。
browserHistory路由變化監聽完整程式碼
let timeStrlet rewriteHis = function(type){ let origin = window。history[type] return function(){ let rs = origin。apply(this, arguments) let e = new Event(type。toLocaleLowerCase()) e。arguments = arguments window。dispatchEvent(e) return rs }}window。history。pushState = rewriteHis(‘pushState’)window。history。replaceState = rewriteHis(‘replaceState’)window。addEventListener(‘onload’,(e)=>{ timeStr = new Date()。getTime()})window。addEventListener(‘popstate’,()=>{ let t = new Date()。getTime() - timeStr timeStr = new Date()。getTime() console。log(‘待了時長popstate:’+ t)})window。addEventListener(‘pushstate’,()=>{ let t = new Date()。getTime() - timeStr timeStr = new Date()。getTime() console。log(‘待了時長pushstate:’+ t)})window。addEventListener(‘replacestate’,()=>{ let t = new Date()。getTime() - timeStr timeStr = new Date()。getTime() console。log(‘待了時長replacestate:’+ t)})
這裡的演示就不做任何儲存了,喜歡的話,自己做一下
hashHistory
hashHistory
就簡單得不行了,直接監聽
hashchange
就行
window。addEventListener(‘hashchange’,()=>{ let t = new Date()。getTime() - timeStr timeStr = new Date()。getTime() console。log(‘待了時長:’+ t)})
到這裡為止,單頁面應用,多頁面應用怎麼去獲取使用者待在該頁面的時長就說完了。也不是很理解,挺簡單的。
後續
你以為這就完事了吧,還有一個奇怪的問題。
const router = new VueRouter({ mode:‘hash’, routes:[。。。]})
我在測試
hash路由
切換的時候,看會不會觸發
window。addEventListener(‘hashchange’,()=>{})
,奇怪的事情發生了,它沒有觸發,卻觸發了
自定義window。addEventListener(‘replacestate’,()=>{}和window。addEventListener(‘pushstate’,()=>{})等History API
,究竟是為什麼?我明明把它設定為hash路由了,為什麼還會觸發History API?帶著這個疑問,我忍不住的去看了vue-router的原始碼,最後,解開了自己的疑問,看下面:
在vue-router的hash路由實現檔案
有這麼一段程式碼,在
supportsPushState
為
false
時,才會走
else
邏輯,
else
的邏輯才會觸發
window。addEventListener(‘hashchange’,()=>{})
,那它為什麼不走?
supportsPushState
又是什麼?
pushState,replaceState
又是怎麼實現的?為什麼它會觸發自定義事件?
supportsPushState
來看看supportsPushState是什麼
上面是supportsPushState的邏輯,看到這邏輯,是不是瞬間就明白了為什麼不走
else
邏輯的hash語句了。當滿足這些條件才會走else語句
(ua。indexOf(‘Android 2。’) !== \-1 || ua。indexOf(‘Android 4。0’) !== \-1) && ua。indexOf(‘Mobile Safari’) !== \-1 && ua。indexOf(‘Chrome’) === \-1 && ua。indexOf(‘Windows Phone’) === \-1
,否則其他都是走
基於History API實現的Hash-router
。
pushState , replaceState
再來看看這兩個api的實現
原來它們都是呼叫了
History API
實現,這就解開了為什麼它會觸發自定義事件的原因了。
看原始碼指引
如果你不相信,我比比的話,可以自行看看原始碼。