列表页曝光埋点实现
以商品为例
要求
- 商品一半以上出现在视窗中时 上报该行的商品
- 快速滑动过去的商品不上报
- 滑动过程中如果一行商品一直未消失在视野中(一半以上),不能重复上报
- 滑出视野的商品,再次滑入视野时需要再次上报
分析
需要以下信息
- 商品所在行的高度rowHeight(固定值)
- 商品的可视区域的高度contentHeight(半固定值,不考虑浏览器的resize)
- 可视区域距离视窗顶部的高度headHeight(固定值)
- 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 } }}
图示
使用
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)