如何用 IntersectionObserver 跟踪长页视频广告的真正观看时长?
- 内容介绍
- 相关推荐
本文共计1007个文字,预计阅读时间需要5分钟。
不能仅依靠 isIntersecting 切换启动计时,必须结合 intersectionRatio 和 performance.now() 记录连续表达标记时间,否则报告的观察时长会被广告平台判定为无效。
为什么 isIntersecting 真假切换不适用于广告有效观看判断
广告平台(如 Google Ad Manager、穿山甲)明确要求「至少 50% 面积在视口内持续 ≥1 秒」才算一次有效曝光。而 isIntersecting 在元素哪怕只有 1px 进入视口时就为 true,滚动飞快掠过时也会触发,导致:
- 用户快速滑动页面,广告仅露边角 0.2 秒,
isIntersecting却从false → true → false走完一整套,被误计为“完整曝光” - 父容器有
overflow: hidden或视频被transform: scale(0.99)微调,isIntersecting可能始终为false,漏报真实可见场景 -
isIntersecting不区分“刚进”和“大部分已进”,无法支撑业务定义的“有效”阈值
正确配置 threshold 和判断逻辑的关键点
必须显式声明关键比例点,让浏览器在 intersectionRatio 达到 0.5 时精准触发回调,而非依赖节流后的模糊状态:
- 初始化 observer 时传入
{ threshold: [0, 0.1, 0.5, 1.0] }—— 这样你一定能捕获到0.5进入和退出的两个时刻 - 回调中只对
entry.intersectionRatio >= 0.5的 entry 做处理,其余直接return - 用
performance.now()记录时间戳,避免Date.now()因系统时钟跳变导致负时长或异常值 - 不要在每次回调都重置定时器;应维护状态:记录首次达标时间
startTime,持续更新lastValidTime,仅在掉出 0.5 后计算lastValidTime - startTime
如何防止重复计时、内存泄漏与 DOM 失效问题
一个广告位可能被多次 observe(比如 React 组件反复挂载/卸载),或用户切走标签页再切回,这些都会干扰计时准确性:
- 每个广告元素绑定前,先检查是否已有挂载的 observer 实例(例如用
element._adObserver属性缓存),避免重复observe() - 在
beforeunload或广告 DOM 被移除前(监听MutationObserver或用element.isConnected === false),强制 flush 当前未上报的时长 - 上报前校验
entry.target.id是否存在且含ad_id字段,避免因服务端动态渲染失败导致上报空ad_id被拒审 - 移动端 WebView(尤其 Android 7–8)可能漏发
intersectionRatio变化,必须做降级:检测'IntersectionObserver' in window,不支持则 fallback 到getBoundingClientRect()+scroll事件节流(100ms)
容易被忽略的细节:视频广告特有的干扰项
视频元素比普通 div 更容易受渲染状态影响,以下三点常被跳过但直接影响有效性:
-
video.readyState必须 ≥HTMLMediaElement.HAVE_METADATA才能安全调用play()或读取尺寸;否则boundingClientRect可能为{ width: 0, height: 0 },导致intersectionRatio恒为 0 - CSS 中
opacity: 0、visibility: hidden或transform: translateZ(-9999px)会让元素不可见但仍在 DOM 中,IntersectionObserver仍会计算交集——需额外判断getComputedStyle(video).opacity !== '0'等 - Chrome 97+ 对后台标签页中媒体操作加了限制,切回页面后
video.paused可能已是true,但intersectionRatio还没刷新;此时不应仅凭 ratio 上报“正在观看”,要结合document.hidden状态过滤
本文共计1007个文字,预计阅读时间需要5分钟。
不能仅依靠 isIntersecting 切换启动计时,必须结合 intersectionRatio 和 performance.now() 记录连续表达标记时间,否则报告的观察时长会被广告平台判定为无效。
为什么 isIntersecting 真假切换不适用于广告有效观看判断
广告平台(如 Google Ad Manager、穿山甲)明确要求「至少 50% 面积在视口内持续 ≥1 秒」才算一次有效曝光。而 isIntersecting 在元素哪怕只有 1px 进入视口时就为 true,滚动飞快掠过时也会触发,导致:
- 用户快速滑动页面,广告仅露边角 0.2 秒,
isIntersecting却从false → true → false走完一整套,被误计为“完整曝光” - 父容器有
overflow: hidden或视频被transform: scale(0.99)微调,isIntersecting可能始终为false,漏报真实可见场景 -
isIntersecting不区分“刚进”和“大部分已进”,无法支撑业务定义的“有效”阈值
正确配置 threshold 和判断逻辑的关键点
必须显式声明关键比例点,让浏览器在 intersectionRatio 达到 0.5 时精准触发回调,而非依赖节流后的模糊状态:
- 初始化 observer 时传入
{ threshold: [0, 0.1, 0.5, 1.0] }—— 这样你一定能捕获到0.5进入和退出的两个时刻 - 回调中只对
entry.intersectionRatio >= 0.5的 entry 做处理,其余直接return - 用
performance.now()记录时间戳,避免Date.now()因系统时钟跳变导致负时长或异常值 - 不要在每次回调都重置定时器;应维护状态:记录首次达标时间
startTime,持续更新lastValidTime,仅在掉出 0.5 后计算lastValidTime - startTime
如何防止重复计时、内存泄漏与 DOM 失效问题
一个广告位可能被多次 observe(比如 React 组件反复挂载/卸载),或用户切走标签页再切回,这些都会干扰计时准确性:
- 每个广告元素绑定前,先检查是否已有挂载的 observer 实例(例如用
element._adObserver属性缓存),避免重复observe() - 在
beforeunload或广告 DOM 被移除前(监听MutationObserver或用element.isConnected === false),强制 flush 当前未上报的时长 - 上报前校验
entry.target.id是否存在且含ad_id字段,避免因服务端动态渲染失败导致上报空ad_id被拒审 - 移动端 WebView(尤其 Android 7–8)可能漏发
intersectionRatio变化,必须做降级:检测'IntersectionObserver' in window,不支持则 fallback 到getBoundingClientRect()+scroll事件节流(100ms)
容易被忽略的细节:视频广告特有的干扰项
视频元素比普通 div 更容易受渲染状态影响,以下三点常被跳过但直接影响有效性:
-
video.readyState必须 ≥HTMLMediaElement.HAVE_METADATA才能安全调用play()或读取尺寸;否则boundingClientRect可能为{ width: 0, height: 0 },导致intersectionRatio恒为 0 - CSS 中
opacity: 0、visibility: hidden或transform: translateZ(-9999px)会让元素不可见但仍在 DOM 中,IntersectionObserver仍会计算交集——需额外判断getComputedStyle(video).opacity !== '0'等 - Chrome 97+ 对后台标签页中媒体操作加了限制,切回页面后
video.paused可能已是true,但intersectionRatio还没刷新;此时不应仅凭 ratio 上报“正在观看”,要结合document.hidden状态过滤

