转载自: 前端大全
前言:为什么要做这个监测用户停留的呢?原因很简单,如果我们要分析这个页面对我们的产品有没有价格,那么用户浏览的时长是一个很关键的点,如果每个用户每天都在这个页面停留两个小时以上,那么我们会觉得这个页面的价值很高;如果一个页面一个月也没几个用户去浏览,那我们就会有疑问,这个页面对我们的产品还有价值吗?我们的产品后续还要保留它?这些需求都是可以让我们考虑是否要去获取用户停留时长这个功能的。
针对哪些应用?
- 多页面应用
- 单页面应用(本文以vue为例子)
如何去获取用户停留的时长?
在监测功能的时候,我们首要考虑的就是,我们的监测代码不能影响我们现在的业务代码,和以后的业务代码。
多页面应用
在多页面应用,要获取用户的停留时间还是挺简单的,看看下面几个 API
- onload (页面加载完后)
- onbeforeunload (页面卸载前,也就是点击×的时候)
- onpageshow (页面显示的时候)
- onpagehide (页面隐藏的时候)
经过自己的一点小测试,发现无论在关闭的时候(也就是点击×),还是隐藏的时候(也就是点击缩小)都会触发 onpagehide
;也无论是在首次加载,还是刷新的时候,都会触发 onpageshow
,但是刷新的时候会先触发 onpagehide
,在触发 onpageshow
。其他两个API也是类似的,最后,个人选择了用 onpageshow
和 onpgaehide
这两个 API 获取用户停留的时长,你也可以用其他两个 API 做。只要在 onpageshow
初试时间值,在 onpagehide
的时间求出差值,然后上传到后台就行。
let stopTime
window.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
,在项目中可以传到后台,然后通过分析,再可视化展示出来。
单页面应用
单页面应用可能,会复杂一点,但是也复杂不到哪里去。
单页面应用的路由跳转,都是基于 H5 的 History API (browserHistory)
和 Hash (hashHistory)
实现的。
browerHistory
单页面的 browserHistory
路由 都是基于 H5 的 Histroy API 实现的,我们只要监听 popstate
就可以知道,点击前进后退按钮改变的 url 变化,url 发生变化,我们就能统计用户在该页面待了多长时间,代码如下
let timeStr
window.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.repalce,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 timeStr
let 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('hasgchange', () => {})
,奇怪的事情发生了,它没有触发,却触发了自定义window.addEventListener('replaceSrare', () => {})
和 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
实现,这就解开了为什么它会触发自定义事件的原因了。
看源码指引