我們在處理模塊曝光埋點時,需要根據(jù)頁面滾動的位置判斷模塊是否可見(被曝光)。Web 上傳統(tǒng)方法是增加頁面 scroll 監(jiān)聽事件,根據(jù)滾動位置與模塊位置進行對比判斷,小程序上也可以使用這種方法,但現(xiàn)在有更便捷優(yōu)雅的替代方案 —— IntersectionObserver 對象。
IntersectionObserver 對象
IntersectionObserver 對象,用于推斷某些節(jié)點是否可以被用戶看見,下面將介紹相關(guān)的 API:
(1) 創(chuàng)建
通過 this.createIntersectionObserver 創(chuàng)建,該方法可傳入的參數(shù)有三個:
- <u style="word-wrap: break-word; margin: 0px; padding: 0px; text-decoration: none; font-style: normal;">thresholds</u> :數(shù)值數(shù)組,代表相交比例的閾值(可有多個,取值范圍
[0,1]),當相交到達該閾值時會觸發(fā)一次監(jiān)聽回調(diào),在曝光埋點場景下設(shè)置為中間位置[0.5]即可; - <u style="word-wrap: break-word; margin: 0px; padding: 0px; text-decoration: none; font-style: normal;">initialRatio</u> :初始相交比例,如果方法調(diào)用時檢測到的相交比例與這個值不相等且達到閾值,則會觸發(fā)一次監(jiān)聽器的回調(diào)函數(shù),在曝光埋點的場景下設(shè)置為默認值0即可;
- <u style="word-wrap: break-word; margin: 0px; padding: 0px; text-decoration: none; font-style: normal;">observeAll</u> :是否同時觀測多個目標節(jié)點;
(2) 設(shè)置參考區(qū)域
設(shè)置參考區(qū)域的方法有兩個: io.relativeToViewport() 和 io.relativeTo('selector', { ...margins }) ,如果判斷相交參考區(qū)域是窗口,則使用前者,曝光埋點的場景下就使用這個;后者可用選擇器設(shè)置其他節(jié)點作為相交的參考區(qū)域。
(3) 監(jiān)聽
開始監(jiān)聽方法: io.observe(selector, callback) ,selector代表目標模塊的選擇器,當它和參考區(qū)域相交達到閾值比例時,會觸發(fā) callback 回調(diào)函數(shù),回調(diào)函數(shù)接受如下幾個參數(shù)(在該場景中暫時都不會用到):
<u style="word-wrap: break-word; margin: 0px; padding: 0px; text-decoration: none; font-style: normal;">intersectionRatio</u> : 兩者相交比例;
<u style="word-wrap: break-word; margin: 0px; padding: 0px; text-decoration: none; font-style: normal;">time</u> :相交檢測時的時間戳;
各種邊界:
<u style="word-wrap: break-word; margin: 0px; padding: 0px; text-decoration: none; font-style: normal;">intersectionRect</u> :相交區(qū)域的邊界;
<u style="word-wrap: break-word; margin: 0px; padding: 0px; text-decoration: none; font-style: normal;">boundingClientRect</u> :目標邊界;
<u style="word-wrap: break-word; margin: 0px; padding: 0px; text-decoration: none; font-style: normal;">relativeRect</u> :參照區(qū)域的邊界;
(4) 取消監(jiān)聽
當頁面退出時記得要取消監(jiān)聽:io.disconnect()。
監(jiān)聽相交區(qū)域類
我們可以設(shè)計一個類,用來處理監(jiān)聽相交區(qū)域的邏輯。
構(gòu)造函數(shù)
首先來看構(gòu)造函數(shù),代碼如下:
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
}
}
顯然,構(gòu)造函數(shù)傳入了一些重要的參數(shù),包括 createIntersectionObserver 所需要的三個參數(shù):thresholds, initialRatio, observeAll 和上下文 context ;設(shè)置參考區(qū)域所需的 relativeTo ;監(jiān)聽方法所需的目標模塊選擇器 selector 。
最后還有 IntersectionObserver 類監(jiān)聽調(diào)用時需要的三個參數(shù):
- onEach:每一次觸發(fā)監(jiān)聽調(diào)用時,也會調(diào)用
onEach方法; - onFinal:在觸發(fā)監(jiān)聽調(diào)用一段時間
delay后,會調(diào)用一次onFinal方法。在模塊曝光埋點場景下,如果頁面在快速滾動時,每次的監(jiān)聽觸發(fā)都上報埋點,一時間請求會堆積很多,所以需要onFinal方法,在一段時間后統(tǒng)一上報曝光埋點; - delay:調(diào)用
onFinal方法的間隔時間;
監(jiān)聽
要想開始監(jiān)聽相交區(qū)域,需要先創(chuàng)建監(jiān)聽器,設(shè)置完相交區(qū)域后再開始監(jiān)聽,關(guān)鍵代碼如下:
_createObserver() {
const opt = this.$options
const observerOptions = {
thresholds: [opt.threshold],
observeAll: opt.observeAll,
initialRatio: opt.initialRatio,
}
// 創(chuàng)建監(jiān)聽器
const ob = opt.context
? opt.context.createIntersectionObserver(observerOptions)
: wx.createIntersectionObserver(null, observerOptions)
// 相交區(qū)域設(shè)置
if (opt.relativeTo) ob.relativeTo(opt.relativeTo)
else ob.relativeToViewport()
// 開始監(jiān)聽
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 // 正在收集監(jiān)聽結(jié)果,不會調(diào)用 onFinal
isCollecting = true
// 設(shè)置延遲調(diào)用 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
}
封裝停止監(jiān)聽的方法:
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…
總結(jié)
當然,曝光埋點也可以使用傳統(tǒng) Web 的監(jiān)聽 scroll 事件的方式。不過,既然小程序提供了 IntersectionObserver API 并且?guī)缀跛锌蛻舳硕家阎С?,那自然就用這種更方便的方式。
另外,在百度小程序和支付寶小程序上也有支持相關(guān)的API,跨端開發(fā)也不用考慮其他小程序不支持。
關(guān)于兼容性
支付包小程序兼容性有待考證,百度可以使用 IntersectionObserver ,不過需要注意this.createIntersectionObserver 非組件是沒有的,只能使用swan.createIntersectionObserver(this);第二點, createIntersectionObserver 的參數(shù)observeAll 需要改成 selectAll (百度小程序代碼片段: swanide://fragment/142c0f60156b1e850dc239553ecffe7b1571810456384 )。
參考文檔
- 官方文檔 ;
- 談談IntersectionObserver懶加載 (Web API 表現(xiàn)形式詳解,和小程序上相同);
作者:w_西城