我们在处理模块曝光埋点时,需要根据页面滚动的位置判断模块是否可见(被曝光)。Web 上传统方法是增加页面 scroll 监听事件,根据滚动位置与模块位置进行对比判断,小程序上也可以使用这种方法,但现在有更便捷优雅的替代方案 —— IntersectionObserver 对象。
IntersectionObserver 对象
IntersectionObserver 对象,用于推断某些节点是否可以被用户看见,下面将介绍相关的 API:
(1) 创建
通过 this.createIntersectionObserver
创建,该方法可传入的参数有三个:
- thresholds :数值数组,代表相交比例的阈值(可有多个,取值范围
[0,1]
),当相交到达该阈值时会触发一次监听回调,在曝光埋点场景下设置为中间位置[0.5]
即可; - initialRatio :初始相交比例,如果方法调用时检测到的相交比例与这个值不相等且达到阈值,则会触发一次监听器的回调函数,在曝光埋点的场景下设置为默认值0即可;
- observeAll :是否同时观测多个目标节点;
(2) 设置参考区域
设置参考区域的方法有两个: io.relativeToViewport()
和 io.relativeTo('selector', { ...margins })
,如果判断相交参考区域是窗口,则使用前者,曝光埋点的场景下就使用这个;后者可用选择器设置其他节点作为相交的参考区域。
(3) 监听
开始监听方法: io.observe(selector, callback)
,selector代表目标模块的选择器,当它和参考区域相交达到阈值比例时,会触发 callback 回调函数,回调函数接受如下几个参数(在该场景中暂时都不会用到):
intersectionRatio : 两者相交比例;
time :相交检测时的时间戳;
各种边界:
intersectionRect :相交区域的边界;
boundingClientRect :目标边界;
relativeRect :参照区域的边界;
(4) 取消监听
当页面退出时记得要取消监听:io.disconnect()。
监听相交区域类
我们可以设计一个类,用来处理监听相交区域的逻辑。
构造函数
首先来看构造函数,代码如下:
class IntersectionObserver {
constructor(options) {
this.$options = {
context: null,
threshold: 0.5,
initialRatio: 0,
observeAll: false,
selector: null,
relativeTo: null,
onEach: res => res.dataset,
onFinal: () => null,
delay: 200,
...options,
}
this.$observer = null
}
}
显然,构造函数传入了一些重要的参数,包括 createIntersectionObserver
所需要的三个参数:thresholds, initialRatio, observeAll
和上下文 context
;设置参考区域所需的 relativeTo
;监听方法所需的目标模块选择器 selector
。
最后还有 IntersectionObserver 类监听调用时需要的三个参数:
- onEach:每一次触发监听调用时,也会调用
onEach
方法; - onFinal:在触发监听调用一段时间
delay
后,会调用一次onFinal
方法。在模块曝光埋点场景下,如果页面在快速滚动时,每次的监听触发都上报埋点,一时间请求会堆积很多,所以需要onFinal
方法,在一段时间后统一上报曝光埋点; - delay:调用
onFinal
方法的间隔时间;
监听
要想开始监听相交区域,需要先创建监听器,设置完相交区域后再开始监听,关键代码如下:
_createObserver() {
const opt = this.$options
const observerOptions = {
thresholds: [opt.threshold],
observeAll: opt.observeAll,
initialRatio: opt.initialRatio,
}
// 创建监听器
const ob = opt.context
? opt.context.createIntersectionObserver(observerOptions)
: wx.createIntersectionObserver(null, observerOptions)
// 相交区域设置
if (opt.relativeTo) ob.relativeTo(opt.relativeTo)
else ob.relativeToViewport()
// 开始监听
let finalData = []
let isCollecting = false
ob.observe(opt.selector, res => {
const { intersectionRatio } = res
const visible = intersectionRatio >= opt.threshold
if (!visible) return
const data = opt.onEach(res)
finalData.push(data)
if (isCollecting) return // 正在收集监听结果,不会调用 onFinal
isCollecting = true
// 设置延迟调用 onFinal
setTimeout(() => {
opt.onFinal.call(null, finalData)
finalData = []
isCollecting = false
}, opt.delay)
})
return ob
}
对外暴露的公用方法
封装对外的 _createObserver
方法:
connect() {
if (this.$observer) return this
this.$observer = this._createObserver()
return this
}
封装停止监听的方法:
disconnect() {
if (!this.$observer) return
const ob = this.$observer
if (ob.$timer) clearTimeout(ob.$timer)
ob.disconnect()
this.$observer = null
}
使用方法
import IntersectionObserver from './intersection-observer.js';
const ob = new IntersectionObserver({...})
ob.connect()
详见代码片段: developers.weixin.qq.com/s/lqUakfmM7…
总结
当然,曝光埋点也可以使用传统 Web 的监听 scroll 事件的方式。不过,既然小程序提供了 IntersectionObserver API 并且几乎所有客户端都已支持,那自然就用这种更方便的方式。
另外,在百度小程序和支付宝小程序上也有支持相关的API,跨端开发也不用考虑其他小程序不支持。
关于兼容性
支付包小程序兼容性有待考证,百度可以使用 IntersectionObserver
,不过需要注意this.createIntersectionObserver
非组件是没有的,只能使用swan.createIntersectionObserver(this)
;第二点, createIntersectionObserver
的参数observeAll
需要改成 selectAll
(百度小程序代码片段: swanide://fragment/142c0f60156b1e850dc239553ecffe7b1571810456384 )。
参考文档
- 官方文档 ;
- 谈谈IntersectionObserver懒加载 (Web API 表现形式详解,和小程序上相同);
作者:w_西城