进行数据上报的时候,经常会遇到列表数据曝光上报的问题,只对在当前可视范围内的数据内容进行曝光上报,而对于未在可视范围内的数据不进行曝光上报,等待用户滚动页面或者区域使元素出现在可视范围内时才进行曝光上报。
解决方案
目前针对此类问题,主要有两种解决方案。
方案一:监听页面或者区域 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)
}
})
}
优点:兼容性好
缺点:
- 需要关注页面或者区域的 scroll 事件
- 频繁的 scroll 事件,性能问题
方案二:通过 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)
})
优点:
- 无须关注 scroll
- 回调是异步触发,不会频繁触发,性能好
缺点:兼容性不好?
实际上,针对兼容性问题,w3c 官方提供了对应 polyfill, 因此 intersectionObserver
用于生产是可行的。
总结
笔者在实际运用中,通过 IntersectionObserver
封装了一个简单的调用库,应用于可视化埋点 sdk 中,用于解决元素曝光问题,如下
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
调用方法:
function report() {}
var exposurer = new Exposure((node) => {report(node)
})
exposurer.observe(".item)