一个从内容曝光数据上报学到的新姿势——IntersectionObserver

112 阅读3分钟

上周接到业务侧的一个数据埋点上报的需求,要求上报内容模块的曝光。本以为只是举手之劳的事情,结果却因此发现了新姿势。

一般情况下,我们直接在组件初始化的时候直接上报,例如在React的函数组件:

useEffect(() => { 
  stat('PV') 
}, []) 

但这次有点不一样,该内容模块需要滚动页面才能被看到,业务侧想要更加精准的在内容模块出现在窗口时才上报曝光事件。

第一反应

既然需要滚动才能看到,那自然而然的想法就是,监听窗口的scroll事件,并计算该内容模块是否出现在浏览器的视口(viewport)内,是的话就进行数据上报。

但这个方案有一定的性能问题,每次页面滚动都执行回调函数进行判断,如果页面上多个内容模块都需要做类似的数据上报的话,那就有点刺激了。更有甚者,如果页面有部分内容是可收缩展开,那意味着还得动态计算各个内容模块的位置。

更优雅的方案

这时候,一个叫IntersectionObserver的api出现了,稍稍看了 MDN - IntersectionObserver 官方文档 就有眼前一亮的感觉。

它是一个用于观察指定元素在祖先元素/文档视口的显示状态的方法。它提供边界元素、边界元素偏移量、触发监听阈值等3个可自定义参数。

回到我的需求,我只需要初始化一个IntersectionObserver,指定观察监听的DOM元素(也就是我需要进行曝光上报的内容模块),并处理回调函数逻辑进行上报,最后停止监听(保证只上报1次)即可。

    // 内容模块曝光上报
    const viewObsvr = new IntersectionObserver(els => {
      // intersectionRatio小于等于零,表示不在指定视野区域内
      if (els[0].intersectionRatio <= 0) {
        return
      }
      console.log('内容模块曝光')
      viewObsvr.disconnect()
    })
    // 指定观察DOM元素
    viewObsvr.observe(document.querySelector('#mybox'))

更多的使用场景

由此想到,还有很多的使用场景可以适用 IntersectionObserver 作为解决方案。(特别是通过监听窗口的scroll事件的场景)

悬浮栏

很多网页都会在滚动浏览的时候,出现一个悬浮工具栏,方便用户进行跳转到指定章节、回到顶部、分享等操作。这时我们可以指定一个DOM元素,例如下面代码中的页头,并定义了偏移量200px,当向下偏移这个页头元素200px时,才会触发监听事件。

    // 悬浮栏
    const navElm = document.querySelector('#nav')
    const headObsvr = new IntersectionObserver(els => {
      if (els[0].intersectionRatio > 0) {
        console.log('悬浮栏:隐藏')
        navElm.style.display = 'none'
        return
      }
      console.log('悬浮栏:显示')
      navElm.style.display = 'block'
    }, {
      rootMargin: '200px 0px 0px 0px'
    })
    headObsvr.observe(document.querySelector('#header'))

列表下拉加载

列表滚动/下拉加载也是一个非常常规的功能,如果通过scroll事件监听,就需要不断获取列表高度和判断滚动距离,为了避免事件的频繁触发,还需要引入截流和防抖逻辑代码。而IntersectionObserver则只会在出现的时候触发一次,代码也非常简洁直观,简直就是程序员心中的白月光。

    // 列表下拉加载
    let i = 1
    const loadingObsvr = new IntersectionObserver(els => {
      console.log(els[0].intersectionRatio)
      if (els[0].intersectionRatio <= 0) {
        return
      }
      console.log('加载提示出现')
      // 请求数据逻辑
      // TODO: 
    })
    loadingObsvr.observe(document.querySelector('#loading'))

背景色变化

这是一个有点花里胡哨的场景,灵感来源于intersectionRatio表示指定DOM展示内容的百分比,那是不是一些CSS的展示属性和动画,可以通过这个数据动态控制,这里以background-color为例。

    // 背景色变化
    const colorElm = document.querySelector('#colorBox')
    const colorObsvr = new IntersectionObserver(els => {
      colorElm.style.backgroundColor = `rgba(${240 - 120*els[0].intersectionRatio}, ${240 - 100*els[0].intersectionRatio}, ${2240 - 50*els[0].intersectionRatio}, 1)`
    }, {
      threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
    })
    colorObsvr.observe(colorElm)

兼容性

这个不算常用的api,其实早在Chrome 58就已经实现,目前在各大浏览器都已经广泛支持,甚至连移动端的都支持的非常好。

CanIUse - IntersectionObserver