5

列表页曝光埋点实现

以商品为例

要求

  1. 商品一半以上出现在视窗中时 上报该行的商品
  2. 快速滑动过去的商品不上报
  3. 滑动过程中如果一行商品一直未消失在视野中(一半以上),不能重复上报
  4. 滑出视野的商品,再次滑入视野时需要再次上报

分析

需要以下信息

  1. 商品所在行的高度rowHeight(固定值)
  2. 商品的可视区域的高度contentHeight(半固定值,不考虑浏览器的resize)
  3. 可视区域距离视窗顶部的高度headHeight(固定值)
  4. content的滚动高度(与scroll事件相关,考虑到滑动快时不触发上报,需要throttle)

实现

/**
 * 滚动事件处理
 * @param {number} headHeight content区域距离顶部的高度
 * @param {number} rowHeight 每一行的高度
 * @returns {Function}
 */
export function handleScroll(headHeight, rowHeight) {
  let lastActive = []
  let deactived = []
  /**
   * @param   {number} contentTop 区域的top值
   * @return  {Array}  当前活跃的的行
   */
  return function(contentTop) {
    let topDiff = contentTop - headHeight
    // 可视区域高度
    let visibleHeight =
      window.innerHeight - (topDiff <= 0 ? headHeight : contentTop)
    /**
     * 当前能显示的行数
     * 显露一半就需要上报 则使用四舍五入
     */
    let rowCount = Math.round(visibleHeight / rowHeight)
    /**
     * 获取当前显示的下标
     */
    let index = topDiff > 0 ? 0 : Math.round(-topDiff / rowHeight)
    let _active = Array.from({ length: rowCount }).reduce(
      (pre, cur, i) => pre.concat(index + i),
      []
    )
    /**
     * 之前上报过,未从屏幕上消失过的 不上报
     * 之前上报过,从屏幕中消失又出现的 上报
     */
    let active = _active.filter(
      v => !lastActive.includes(v) || deactived.includes(v)
    )
    /**
     * 收集非活跃状态的行,只收集滚上去的元素,active下面的行属于待活跃状态,由于和行的总数相关(商品的总行数知道与否不影响上报),会额外增加不必要的工作 所以此处不做考虑
     */
    deactived = Array.from({ length: index }).map((val, i) => i)
    /**
     * 上次活跃的行,用来避免重复上报
     */
    lastActive = [].concat(deactived).concat(_active)
    return {
      lastActive,
      active,
      deactived
    }
  }
}

图示

5bfca0967daa9

使用

let target = document.getElementById('wrapper')
let onScroll = handleScroll(100, 420)
let _scroll = _.throttle(function(){
 let row = onScroll(target.getBoundingClientRect().y)
 // 此时row.active就是需要上报的行的下标,active可能为空数组
 ...
}, 1000)
target.addEventListener('scroll', _scroll)

Feliks
120 声望0 粉丝