作者:w_西城
我們在處理模塊曝光埋點時,需要根據頁面滾動的位置判斷模塊是否可見(被曝光)。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 表現形式詳解,和小程序上相同);