为准确分析各前端页面实际对用户的吸引力,需要统计的页面元素的曝光数据。曝光的含义比较模糊,具体的统计方式也比较麻烦,本文分享一个前端曝光埋点上报的实现方案。
为了统计曝光数据,首先要做的是,定义什么是曝光,然后制定上报数据的策略。
根据我们业务的实际情况,我们可以这样设计:
具体的代码实现如下:

绑定指令后的元素:


观测元素的几种情况:
require('intersection-observer');
const MIN_OBSERVE_TIME = 500;
const OBSERVE_REPEAT_TIME = 1000;
const REPORT_REPEAT_TIME = 1000;
// 获取IntersectionObserver的单例
class ReportObserver {
constructor() {
this.instance = null;
this.intersectionObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
handleEnter(entry);
} else {
handleExit(entry);
}
});
});
// 初始化定时器
initInterval();
}
observe(el, reportData) {
el.setAttribute('report-data', JSON.stringify(reportData));
el.setAttribute('guid', guid());
this.intersectionObserver.observe(el);
}
// 获取IntersectionObserver的实例
static getInstance() {
if (!this.instance) {
this.instance = new ReportObserver();
}
return this.instance;
}
}
// 元素X进入窗口,记录到sessionStorage的to-observe队列(如果已存在,就不加入队列),
// 结构为 {stime:观测到的时间, id:元素ID, data:待上报数据,hasObserve:false}。
const handleEnter = function (entry) {
const dom = entry.target;
const data = dom.getAttribute('report-data');
const id = dom.getAttribute('guid');
const stime = new Date().getTime();
const hasObserve = false;
const observeData = { id, data, stime, hasObserve };
if (!findToObserve(id)) {
pushToObserve(observeData);
}
};
// 元素X退出窗口:
// 1、从to-observe队列获取X的stime,如果(当前时间-stime)>=500ms而且hasObserve为false,将X元素的数据推入to-report的队列。
// 2、无论何种情况,元素X都要推出to-observe队列。
const handleExit = function (entry) {
const dom = entry.target;
const id = dom.getAttribute('guid');
const etime = new Date().getTime();
const value = findToObserve(id);
if (value && etime - value.stime >= MIN_OBSERVE_TIME && !value.hasObserve) {
pushToReport(value);
}
deleteFromToObserve(id);
};
// 初始化定时器
const initInterval = function () {
// 曝光定时器
setInterval(() => {
// 如果to-observe队列中存在(当前时间-stime)>=500ms并且hasObserve为false的X,将X的hasObserve置为true,并推入to-report的队列
toObserveList().forEach((value) => {
const etime = new Date().getTime();
if (etime - value.stime >= MIN_OBSERVE_TIME && !value.hasObserve) {
value.hasObserve = true;
pushToObserve(value);
pushToReport(value);
}
});
}, OBSERVE_REPEAT_TIME);
// 上报定时器
setInterval(() => {
// 如果to-report队列存在记录,上报并从to-report移出。
if (toReportList.length) {
postReportData(toReportList);
clearToReport();
}
}, REPORT_REPEAT_TIME);
};
const postReportData = function (dataList) {
// 调用后台接口上报数据
};
export default {
bind(el, binding) {
if (
'IntersectionObserver' in window
&& 'IntersectionObserverEntry' in window
&& 'intersectionRatio' in window.IntersectionObserverEntry.prototype
) {
// 开始监听
ReportObserver.getInstance().observe(el, binding.value);;
}
},
};