不能只靠isintersecting真假切换计时,须结合intersectionratio≥0.5和performance.now()判断持续≥1秒的有效曝光,并用状态机记录起始/结束时间,上报前校验dom存在性与ad_id有效性。

IntersectionObserver 回调中如何准确计算「有效曝光时长」
直接说结论:不能只靠 isIntersecting 的真假切换来启停计时,必须依赖 intersectionRatio 和 boundingClientRect 配合业务阈值判断——因为广告平台普遍要求「至少 50% 面积在视口内持续 ≥1 秒」才算一次有效曝光。
常见错误是监听到 isIntersecting === true 就开始计时,false 就立刻结束并上报。这会导致:滚动飞快掠过广告、或广告仅边缘露一点就触发计时,结果上报大量无效曝光,被平台拒审或扣量。
- 用
threshold: [0, 0.1, 0.5, 1.0]显式声明关键比例点,确保能捕获 50% 进入/退出的精确时刻 - 在回调里检查
entries[0].intersectionRatio >= 0.5才视为“达标可见”,否则忽略 - 用
performance.now()记录时间戳,避免Date.now()受系统时钟调整影响
如何避免重复计时和计时中断导致的误差
IntersectionObserver 的回调不是每帧触发,而是浏览器节流后的离散事件;一次快速滚动可能只收到 2–3 条 entry,中间缺失状态。如果每次回调都重置计时器,会严重低估真实曝光时长。
正确做法是维护一个状态机:记录「达标可见」的起始时间、当前是否处于达标状态、上一次达标时间戳。只在 intersectionRatio 首次 ≥0.5 时记录起始,之后只要保持 ≥0.5 就持续更新「最后达标时间」;一旦掉到 ,就用「最后达标时间 − 起始时间」算出本次连续达标时长。
- 不要用
setTimeout或setInterval补充采样——增加 CPU 开销且无法解决节流本质问题 - 对同一广告位,只允许一个 active timer,防止多次
observe()或重复绑定造成多实例计时 - 在页面卸载前(
beforeunload)或广告元素disconnect()时,强制 flush 当前未上报的时长
移动端 WebView 中 IntersectionObserver 的兼容性陷阱
Android 低版本 WebView(如 Android 7–8 系统自带 WebView)对 IntersectionObserver 支持不完整:部分机型会漏发 intersectionRatio 变化,或始终返回 0;iOS WKWebView 在页面缩放或键盘弹出后,boundingClientRect 可能未及时更新,导致误判可见性。
必须做降级兜底:
- 初始化时检测
'IntersectionObserver' in window,不支持则 fallback 到getBoundingClientRect()+scroll事件轮询(节流至 100ms) - 对 WebView 场景,额外监听
resize和focusin/focusout(键盘),触发手动 re-check - 服务端需容忍客户端上报的时长存在 ±300ms 偏差,不因微小误差拒绝整条曝光日志
上报逻辑里必须校验的三个硬性条件
即使前端计时准确,上报仍可能被广告平台丢弃。必须在发送前同步验证以下三项,缺一不可:
-
duration >= 1000(毫秒)——低于 1 秒直接丢弃,不请求 -
timestamp_end - timestamp_start === duration,且timestamp_start > navigationStart(防伪造时间) - 该广告位的
ad_id已加载完成、未被用户主动关闭(检查 DOM 是否 still in document & style.display !== 'none')
最易被忽略的是第三点:SPA 页面中广告组件可能已被 Vue/React 卸载但 timer 仍在跑,此时上报的 ad_id 对应的 DOM 已不存在,平台会判定为“曝光位置失效”。务必在每次上报前加一层 document.getElementById(adId) 存在性检查。










