页面元素精确曝光与停留时间打点

场景题: “为了精细化了解用户喜好,广告部老板提出了一个极其刁钻的打点需求:在首页有一个横向的长长推荐商品卡片列表(上百个)。用户在上下滑动屏幕或者左右横滑浏览时,必须满足某一个特定的商品卡片完全出现在手机屏幕内部面积超过一半,并且用户在这个卡片上目光停驻停留超过了至少 2 秒,才算做一次'有效且真实的曝光展现(Impression)'并发送埋点上报给大数据。 这种精确的有效漏斗埋点,如果在前端实现?”

单纯地看页面大框架有没有发送 click 是非常粗暴地,我们常常需要极其细粒度的**“元素视野相交感知”+“防抖精准计时漏斗池”**技术。

历史包袱巨雷:scroll + 节流暴算(毁灭)

过去的前端实现极其暴力血腥,如果列表有一千个商品: 给整个 window 或者 list 绑上 onscroll 节流函数侦测,然后在一千次循环里拿出 1000 个商品卡片各自去调极为昂贵的 getBoundingClientRect() 计算它们各自的上下左右边距是不是掉进到了此刻屏幕可视窗口的算数范围边界里。 每次微小的拖拽都会带来整个浏览器世界主线程剧烈地燃烧引发卡顿。该方案对于业务来说已是被无情嘲讽唾弃的老古董(可参阅无限滚动中提到的相同缺陷)。

破局的核心兵器:IntersectionObserver(重见天曙)

现代浏览器的极致恩惠。他正是专门为了这类“两物体互相侵占甚至遮盖、进入相交”应运而生的神明接口:交叉观察器。利用大名鼎鼎的 Intersection Observer API,计算完全被浏览器在后台使用 C++ 异步且极度高效地静默接管掉,一点都不消耗卡顿前台 JS。

// ====== 埋点方案架构大图 ======

// 1. 设置监视阈值(极其关键的参数:露出面积极致追踪)
const options = {
  root: null, // 默认采用用户最大的整个手机 / 电脑浏览器视窗
  // threshold: 0.5 意思是:只有当目标元素有 50% 面积跨过了门槛进入了屏幕视区,才触发回调!此即“面积曝光条件满足”
  threshold: 0.5 
};

// 2. 一个挂载着长停留时钟的池子大字典(用来记录谁正在被凝视)
const visibleTimers = new Map();

// 3. 构建高精尖监视系统
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    // 拿到被监视到发生动作的那个该死的目标节点(比如商品卡片)
    const targetEl = entry.target;
    const adId = targetEl.getAttribute('data-ad-id'); 

    // === 【情况 A】当卡片猛地出现,且超 50% 落入视口开始被凝视时 ===
    if (entry.isIntersecting) {
      // 一进入我们就设定一颗延迟 2 秒才引爆的打点炸弹雷!(防抖)
      const timer = setTimeout(() => {
        console.log(`📡【极其精细】精准上报:广告商品 ${adId} 有效且深度停留曝光超2秒达成!发给埋点服务!`);
        sendBeaconToLogServer({ id: adId, type: 'view', duration: 2000 });
        
        // 可选保护:成功上报后把此表踢出监视,防止用户反复上下拉扯作弊多次计算曝光
        observer.unobserve(targetEl); 
      }, 2000); // 核心要求之二:足足看上 2 秒起步
      
      // 必须保存这颗雷进字典!
      visibleTimers.set(adId, timer);
      
    } else {
      // === 【情况 B】当卡片还未停留够 2 秒就突然飘出了屏幕 ===
      if (visibleTimers.has(adId)) {
         // 那说明用户刚才只是极度快速非常敷衍地一滑而过,目光一瞥,根本没有深度停留!
         // 我们无情且迅速地物理掐死拆除那颗刚设定好还没爆炸的上报雷!将这条假数据扼杀。
         clearTimeout(visibleTimers.get(adId));
         visibleTimers.delete(adId);
      }
    }
  });
}, options);

// 4. 将全场上百个需要深度刺客监视的商品元素通通注册!
document.querySelectorAll('.goods-card-item').forEach(card => {
  observer.observe(card);
});

全局生命周期防漏网之鱼(加分秀项)

如果用户正满怀好感地凝视在一张海报上。才过了极其漫长 1.8秒 ,还没有达到你极其精通设立的 2秒 上报引爆阀值门槛。用户忽然老板来了心切惊慌地关掉了手机或者强杀了浏览器标签(Tab 强行销毁 / 用户切出)。这样极端的断绝情景由于计时器被同归于尽消灭,你岂不是错拿并流失了足足 1.8 秒的高质量行为停留时间?

如何捕捉这 1.8 秒残存漏网的数据价值?

这个时候大前端要熟知一重隐藏深度的宏事件:利用前文追踪讲过的信标神力:上兵伐谋发丧 sendBeacon 与页面临终感知回调 visibilitychange / pagehide。 在页面即将陨落时,对刚刚所保留的那个长停池(visibleTimers 字典)中尚未完全爆炸引信的那些定时器实体卡片,统系统一清算剩余已停留了的当前毫秒逝去额度时间差,借助 navigator.sendBeacon 在标签被销毁的一瞬间像流星一样极其凶猛强力且不被拦截腰斩地统一打点最后遗言,发往并喂饱数据组渴求极其苛刻的数据库。

此乃现代成熟 C 端精耕细作极致转化大厦搭建的最终技术修养与体现。