IntersectionObserver是判断动态内容真实曝光的唯一可靠方案,但threshold设为0会漏报“闪现”曝光,须检查intersectionRatio>0并采用多档阈值[0,0.1,0.5,1.0]记录最高比例;有效曝光需满足≥50%可见且持续≥1秒,配合performance.now()、visibilitychange监听及时间窗口去重;CTR分母必须与有效曝光严格对齐,通过data-ad-id绑定和本地WeakMap匹配实现精准归因;上报应延迟执行、首次曝光即unobserve,并按服务端时间归因与采样控制。

用 IntersectionObserver 判断元素是否真实曝光
动态内容(如懒加载广告、瀑布流商品卡)无法靠初始 DOM 状态判断是否被用户看到,IntersectionObserver 是唯一可靠方案。但直接设 threshold: 0 会漏报——比如元素刚进视口就因滚动或卸载而消失,isIntersecting 可能为 false,但实际已“闪现”过。
必须检查 entry.intersectionRatio > 0,而非只看 isIntersecting;对关键广告位建议用多档阈值:[0, 0.1, 0.5, 1.0],这样能捕获从“一像素可见”到“完全可见”的全过程,并记录最高达成比例,避免归零误判。
- 不要在回调里直接调
fetch()上报,用queueMicrotask()或setTimeout(() => {}, 0)延迟,防止高频触发导致重复或堆积 - 首次确认曝光后立刻调
observer.unobserve(element),否则滚动来回几次就会重复上报 - 若需支持“再次进入仍统计”,改用计数器 + 时间窗口(如 30 秒内仅计一次),别依赖 observer 生命周期
防作弊的有效曝光时长怎么算
只记录“是否曝光”远远不够。广告平台要求“有效曝光”:通常定义为 ≥50% 面积连续可见 ≥1 秒。这需要状态机 + 高精度时间戳配合 IntersectionObserver 实现。
核心逻辑是:当 entry.intersectionRatio >= 0.5 && entry.isIntersecting 时,记 startTime = performance.now();每次回调中持续更新 lastValidTime;一旦条件不满足,立即计算 duration = lastValidTime - startTime,仅当 duration >= 1000 才视为一次有效曝光并上报。
立即学习“前端免费学习笔记(深入)”;
- 必须监听
visibilitychange和pagehide,页面切走或关闭时终止计时,否则后台标签页也能刷出虚假时长 - 对同一元素 5 秒内反复进出视口的,应合并为单次连续曝光,避免刷量
-
transform: scale(0)或opacity: 0不影响intersectionRatio,需额外用getBoundingClientRect()校验渲染尺寸是否为 0
点击率(CTR)分母必须和曝光统计口径严格对齐
很多团队曝光用 IntersectionObserver 统计,点击却用普通 addEventListener('click'),结果 CTR 分母包含所有“曾短暂闪现”的元素,分子却是用户真正点中的——数值必然失真。CTR 的分母必须是“有效曝光次数”,即满足 ≥50% + ≥1 秒的那次上报所对应的唯一 ID。
实操上,每个动态内容元素需绑定唯一标识(如 data-ad-id="ad_123"),曝光上报时带上该 ID 和时间戳;点击事件触发时也读取同个 data-ad-id,再查本地 Map 或 WeakMap 中是否存在对应的有效曝光记录(且未超时)。只有匹配成功才计入分子。
- 不能把所有点击都算进 CTR,要过滤掉曝光未发生、或曝光已过期(如超过 30 分钟)的点击
- 服务端聚合时,必须按
ad_id + date维度 join 曝光日志与点击日志,不能只靠前端拼接 - 如果后端做归因,注意客户端时钟可能不准,优先用服务端打点时间作为基准
上报失败和采样控制怎么落地
高并发下全量上报会压垮埋点接口,尤其移动端弱网环境。必须分级控制:无效曝光(navigator.sendBeacon(),确保页面卸载前也能送达;高频广告位则启用随机采样。
示例逻辑:if (Math.random() 表示 10% 采样率。采样率需可配置(如从 CDN 加载 JSON 配置),便于灰度和紧急降级。
-
sendBeacon()不支持自定义 header,只能发text/plain或application/x-www-form-urlencoded,数据结构要精简 - 避免在
unobserve后还尝试上报,元素可能已被 GC,element.dataset已不可读 - 错误重试最多 1 次,且仅限网络失败;4xx 错误(如参数非法)不再重试,否则可能放大脏数据
真实业务中最容易被忽略的是:曝光判定和点击归因的时间窗口必须一致,且服务端 join 逻辑必须容忍客户端时钟漂移。哪怕只差 200ms,也可能让一次有效点击无法匹配到对应曝光。



















