進行數據上報的時候,常常會遇到列表數據曝光上報的問題,只對在當前可視範圍內的數據內容進行曝光上報,而對於未在可視範圍內的數據不進行曝光上報,等待用戶滾動頁面或者區域使元素出如今可視範圍內時才進行曝光上報。node
目前針對此類問題,主要有兩種解決方案。npm
scroll
事件,經過getBoundingClientRect
接口取元素的位置與可視窗口進行判斷。function isElementInViewport(el) { var rect = el.getBoundingClientRect(); var width_st = rect.width / 2, height_st = rect.height / 2; var innerHeight = window.innerHeight, innerWidth = window.innerWidth; if ( rect.top <=0 && rect.height > innerHeight || rect.left <= 0 && rect.width > innerWidth ) { return rect.left * rect.right <= 0 || rect.top * rect.bottom <= 0 } return ( rect.height > 0 && rect.width > 0 && ( ( rect.top >= 0 && rect.top <= innerHeight - height_st ) || ( rect.bottom >= height_st && rect.bottom <= innerHeight ) ) && ( ( rect.left >= 0 && rect.left <= innerWidth - width_st ) || ( rect.right >= width_st && rect.right <= innerWidth ) ) ); } var nodes = document.querySelectorAll(".item") function report(node) { // 上報的邏輯 } window.onscroll = function() { nodes.forEach(node => { if( isElementInViewport(node) ) { report(node) } }) }
優勢:兼容性好
缺點:dom
IntersectionObserver
監聽元素是否處於可視範圍function report(node) { // 上報的邏輯 } var intersectionObserver = new IntersectionObserver(entries => { entries.forEach(entry => { if( entry.intersectionRatio > 0 ) { report(entry.target) } }) }) var nodes = document.querySelectorAll(".item") nodes.forEach(node => { intersectionObserver.observe(node) })
優勢:異步
缺點:兼容性很差?性能
實際上,針對兼容性問題,w3c 官方提供了對應 polyfill, 所以intersectionObserver
用於生產是可行的。ui
筆者在實際運用中,經過 IntersectionObserver
封裝了一個簡單的調用庫,應用於可視化埋點 sdk 中,用於解決元素曝光問題,以下this
require('intersection-observer'); // polyfill class Exposure { constructor(callback) { if (!callback || typeof callback !== 'function') { throw new Error("need callback or selector param") return } this.intersectionObserver = new IntersectionObserver((entries) => { entries.forEach(item => { if (item.intersectionRatio > 0) { if (item.target) { callback(item.target, item) this.intersectionObserver.unobserve(item.target) } } }) }); } observe(selector, ignoreExposured) { if (!this.intersectionObserver || !selector) { return } let nodes = [] if( this.isDOM(selector) ) { // dom節點 nodes = [selector] }else { // 選擇器 nodes = document.querySelectorAll(selector) } if (!nodes.length) { return } nodes.forEach(node => { if (!ignoreExposured && node.__wg__tracker__exposured__) { return } node.__wg__tracker__exposured__ = true // 開始觀察 this.intersectionObserver.observe( node ); }) } disconnect() { if (!this.intersectionObserver) { return } this.intersectionObserver.disconnect() } isDOM(obj) { if( !obj ) { return false } if( typeof HTMLElement === 'object' ) { return obj instanceof HTMLElement } if( typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string' ) { return true } return false } } export default Exposure
調用方法:code
function report() {} var exposurer = new Exposure((node) => { report(node) }) exposurer.observe(".item)