计算页面内各模块的曝光时间

3,937 阅读5分钟

计算一个页面内每个模块的曝光时间(停留时间)

产品希望看到投放出去的活动页,用户对其页面内的什么信息比较感兴趣,对什么信息完全不感兴趣。=> 计算页面内每模块的停留时间

第一次听到这个需求,我的大脑开始疯狂运转,然后想到了plan 1, plan 2, plan3...中间还有很多失败想法我已经忘记了,这里方案三是我最终采用的方法。
如果大家有什么更好的想法,欢迎赐教,真心的!!!

方案一:根据页面dom将页面分模块

var bodyChildrenLists = $('body').children()
var bodyChildDomLsit = []
var initHeight = 0
for (var i = 0; i < bodyChildrenLists.length; i++) {
    if (bodyChildrenLists[i].tagName !== 'SCRIPT') {
        bodyChildDomLsit.push({
        className: bodyChildrenLists[i].className,
        height: bodyChildrenLists[i].offsetHeight
      })
    }
}

存在的问题: 不同人的代码风格差异性大,该方案不适合这类代码风格

<body>
    <div class="container">
        <div class="header"></div>
        <div class="nav"></div>
        <div class="footer"></div>
    </div>
</body>

这种方式很好,就是,,,如果大家的代码风格很一致的情况下使用比较好。

方案二:计算出用户打开页面后的所有行为

var scrollTop = 0
var time = Date.now()
window._stayStatus = {
// 记录运动轨迹, down > 1 向下移动 down 向上移动, sliderDis 移动距离, time 移动耗时, initDis 初始距离, initTime 初始时间
    moveData: [],
    enterTime: Date.now()
}
var moveData = window._stayStatus.moveData
var currentMoveIndex = 0
function move () {
    var currentTime = Date.now()
    var currentScrollTop = $(window).scrollTop()
    var dis = currentScrollTop - scrollTop
    var disTime = currentTime - time
    // 上一次滑动页面和这次滑动页面的时间差大于100ms,就视作用户在某一个段时间做了停留
    if (disTime > 100) {
        if (moveData[currentMoveIndex] && moveData[currentMoveIndex].down === 0) {
            moveData[currentMoveIndex].time += disTime
        } else {
            moveData.push({
                down: 0,
                initTime: time, // initTime表示进入该状态的初始时间
                initDis: currentScrollTop, //initDis 表示进入该状态的初始位置
                sliderDis: dis, // 在该状态内滑动的距离
                time: disTime // 在该状态经历的时间(ms)
            })
        }
    } else {
        // 向下滑动
        if (dis >= 0) {
            // 如果之前已经是向下滑动的状态,只需要在原来的数据上累加滑动距离和滑动时间
            if (moveData[currentMoveIndex] && moveData[currentMoveIndex].down > 0) {
                moveData[currentMoveIndex].sliderDis += dis
                moveData[currentMoveIndex].time += disTime
            } else {
                moveData.push({
                    down: 1,
                    initTime: currentTime,
                    initDis: currentScrollTop,
                    sliderDis: dis,
                    time: disTime
                })
            }
        } else {
            if (moveData[currentMoveIndex] && moveData[currentMoveIndex].down < 0) {
                moveData[currentMoveIndex].sliderDis += dis
                moveData[currentMoveIndex].time += disTime
            } else {
                moveData.push({
                    down: -1,
                    initTime: currentTime,
                    initDis: currentScrollTop,
                    sliderDis: dis,
                    time: disTime
                })
            }
        }
    }
    currentMoveIndex = moveData.length - 1
    time = currentTime
    scrollTop = currentScrollTop
  }
  window.onscroll = function (e) {
    move()
  }

根据以上方法获取到的数据如下: 1559048450563_图片.png

表示:用户在距顶部2px时停留了2728ms后,向下滑动了612px,滑动时间为595ms,然后又在距顶部612px停留了8649ms,最后向上滑动了604px,经历了167ms。

存在的问题: 最后得到的数据量虽然不会很大,但是将这样的数据给数据组分析,存在一定的难度。这是在没有和产品对接时,自己想的办法,有点想复杂了。但是这种方式可以比较生动模拟出用户的行为。

方案三: 固定模块尺寸,计算每模块的停留时间

与我的产品对接了基本的规定:

  • 每1300px高度(大约一屏高度)作为为一个模块,进行埋点统计。
  • 每屏曝光范围大于400px(大约三分之一屏高度)时作为有效曝光,开始记录时长。
  • 每一模块的上报时间均为在模块内滑动时间及静止停留时间加和。
  • 设当前模块为模块0,当用户未到达模块1时,反复滑动时间均记做模块0内时间。
  • 设当前模块为模块0,当用户到达模块1后又通过滑动行为返回模块0,此时会重新记录一次模块0数据
  • 最后一次上报时间为用户离开该页面(进入下一流程页面或关闭浏览器),需统计能够监测到的用户离开行为及场景。

根据上面的要求我做了一个demo,可以清晰看到我计算效果,链接可看 munan2.github.io/computeStay…

根据以上需求,我的代码如:

new Vue({
    el: '#app',
    data: {
        movedata: [],
        scrollTop: $(window).scrollTop(),
        time: Date.now(),
        stayTime: 0
    },
    mounted () {
        // 部分页面存在页面滚动到某一高度时,刷新后页面也会固定在该高度的问题,初始化movedata数据
        var index = parseInt(this.scrollTop / 1300) + 1
        for (var i = 0; i <= index; i++) {
          this.movedata.push({
            pos: i * 1300,
            time: 0
          })
        }
        window.onscroll = () => {
          this.scrollTop = $(window).scrollTop()
        }
        setInterval(() => {
          var currentTime = Date.now()
          var disTime = currentTime - this.time
          // 计算当前是展现在屏幕中的模块序号
          var currentIndex = parseInt(this.scrollTop / 1300)
          // 计算当前的滚动高度超出满屏的多少
          var length = this.movedata.length
          if (currentIndex + 1 >= length) {
            for (var i = length; i <= currentIndex + 1; i++) {
              this.movedata.push({
                pos: 1300 * i,
                time: disTime
              })
            }
          } else {
            var modeDis = this.scrollTop - this.movedata[currentIndex].pos
            if ((1300 - modeDis) > 400) {
              this.movedata[currentIndex].time += disTime
            }
            if (modeDis > 400) {
              this.movedata[currentIndex + 1].time += disTime
            }
          }
          this.time = currentTime
        }, 1000)
    }
})

该方法的核心思想:

  1. 刚进入页面时,计算页面的滚动高度,并确定当前在哪个模块中,当前页面中最多展示两个模块(parseInt(this.scrollTop / 1300)),所以,我们假设已经在展示两个模块了,需要给parseInt(this.scrollTop / 1300)+1
  2. 数组中的数据的排序是按照位置来排序的,所以计算到模块的值后,将小于等于index的数据都补到movedata中
  3. 开启定时器1s,计算一下当前的位置的停留时间
  4. 每隔1s计算一下当前滚动到哪一个模块中了(currentIndex),与movedata的长度做比较
  5. 如果currentIndex+1(默认一屏内展示了两个模块)大于movedata.length,说明页面又滚动到了下面,movedata里还没有下面模块的数据,就补上
  6. 如果小于的话,说明页面滚动的范围还在现有的模块数据中,这样判断一下上面的模块暴露出来的高度是否超过400,下面的模块暴露出来的高度是否超过400。

step6的计算思想可归纳为图片:

使用该方法的原因:

  1. 数据量小,一般一个活动页最多五六屏高度,这样movedata数组中的数据量也就五六条,我们在发送日志时,一般请求图片资源的形式(get请求),get请求在数据量过大时会存在数据丢失的问题;
  2. 由于产品的需求是在一个页面内停留时,如果有两个模块存在,希望可以给这两个模块都记上停留时间,如果使用分流的形式,假设我将前10个数据发给了数据,这样我会清除数组中前10条,但是如果停留的时候前一个模块也在曝光内,那么停留时间的计算会有一定的难度。

该方法的缺点: 该方法没办法模拟到用户进入到一个页面的所有行为,如果需要这样的需求,第二种方案比较好