
直答锁屏去吃饭这段挂钟时间不等于用户在看页面。浏览器休眠会限流后台计时器Session 超时和真实停留时长因此是两本账。沿访问时间线把休眠、唤醒、切割三个判定点标出来口径才对得上。想象一个很熟悉的场景中午十二点你打开一个报表页面看数据看到一半手机来了消息顺手锁屏去食堂吃饭。下午一点回来解锁屏幕浏览器还停在那个标签页。问题来了——这中间一个小时算不算这个页面的停留时长Session 有没有超时被切开这个问题之所以值得较真是因为它直接关系到平均浏览时长这个指标的成色。MDN 的 Page Visibility API 文档讲得很清楚当用户把窗口收起、切到别的标签页、或者屏幕锁屏时浏览器会触发 visibilitychange 事件页面进入 hidden 状态。W3C 的 Page Visibility 规范进一步把 hidden 的情形列得很细——窗口收起、后台标签页、锁屏都算。也就是说这一个小时里页面其实早就看不见了。问题在于凡是按打开到关闭的挂钟差来算的时长天然会把锁屏午休也算进去。怎么把这部分剔出去就是计时校验要解决的事。沿一条访问时间线把判定点标出来把一次完整访问铺开休眠、唤醒和会话切割就藏在时间线的几个节点上。下面这张时间线是一次典型的中午锁屏、下午回来的访问。12:00 打开页面visible页面可见计时器正常走第 1 次会话开始计时。12:05 切后台/锁屏hiddenvisibilitychange 触发页面进入 hidden后台计时器开始被限流这也是本次会话的最后一次活动。12:35 Session 切割点关键距 12:05 最后活动已满 30 分钟无活动第 1 次会话在此结束此后到 13:00 之间属于两次会话之间的空档。13:00 回来解锁visible页面重新可见挂钟已过去一小时另起第 2 次会话开始计时。13:20 关闭页面第 2 次会话结束。挂钟跨度一小时二十分钟真正可见活跃时间只有二十五分钟左右。一次访问中可见、隐藏、唤醒三个阶段与 Session 在 12:35 的切割点Session 超时到底按什么算Session会话切割关心的不是用户看了多久而是这两次访问之间隔了多久。主流统计产品通常用挂钟超时来切割例如 Google Analytics 4 默认在 30 分钟无活动后结束一次会话这个时长还可以在后台调整。这样做是有意的——隔了很久又回来本来就该算一次新访问。这意味着 Session 超时和停留时长是两套口径别混为一谈。Session 超时回答这是一次还是两次访问用挂钟、容忍大段空白停留时长回答用户真正看了多久应尽量只算可见时间。访问次数、停留时长这些维度归在网站分析里要注意的是小程序端的指标口径官网并未公开定义这类跨端的会话规则不能凭印象替它补写。举个具体的对照同样一次访问挂钟差口径可能报出一小时二十分钟而可见时间累加口径只有二十五分钟。差出来的五十五分钟就是用户锁屏去吃饭那段、页面早已 hidden 的时间。两种口径没有对错关键是先想清楚你要回答哪个问题——是这个会话从开始到结束跨了多久还是用户实际盯着屏幕看了多久。把这两个问题混在一个指标里报表再漂亮也解释不清。单调时钟和挂钟休眠期行为不一样还要说清代码里用的是哪种钟。performance.now() 是一个单调时钟数值只会往前、不会因为系统校时或 NTP 对时而回退后台标签页被限流的是 setTimeout 这类定时回调而不是这个时钟本身照常走秒。这正是我们在 visibilitychange 回调里用 performance.now() 的差值来结算可见时长、而不去依赖定时器是否准点触发的原因。至于整台电脑合盖这种 OS 级挂起performance.now() 是否继续走动因平台而异Windows 上基本继续走其他平台未必所以跨手机和桌面汇总时长时仍要分环境看分布不能只看一个总数。三种计时方案差别在哪把常见的几种做法摆到一张表里优劣一目了然。计时方案怎么算优点失真点纯挂钟差打开到关闭的时间差实现简单锁屏、挂后台全部计入时长虚高定时心跳每隔固定秒上报一次活跃能感知断连后台定时器被限流心跳本身不准可见时间累加只累计 visibilityState 为 visible 的时间贴近真实观看要监听可见性事件实现略复杂下面是一段示意代码演示怎么用 Page Visibility 只累加页面可见的时间而不是盲信挂钟。// 示意代码只累计页面可见时间作为停留时长 let visibleStart null; let visibleMs 0; document.addEventListener(visibilitychange, () { if (document.visibilityState visible) { visibleStart performance.now(); // 单调时钟重新可见开始计时 } else { if (visibleStart ! null) { visibleMs performance.now() - visibleStart; // 隐藏前结算这段可见时间 visibleStart null; } } }); // 关闭页面时上报 visibleMs而不是打开到现在的挂钟差挂钟差、定时心跳、可见时间累加三种方案的失真对比失真程度低→中→高校验时要盯哪些异常上线一种新的计时口径后别只看均值要看分布。如果平均浏览时长突然整体下降往往是把大量开着页面去午休的样本剔出去了——这是对的不是坏数据。如果某台设备、某类浏览器的时长异常高多半是它的后台限流策略不同可见性事件没有按预期触发。需要说明边界上面监听 visibilitychange、按可见状态累加时间是通用的前端计时做法。访问习惯维度沉淀成固定指标团队要做的是在接入时对齐而不是各算各的。还有一个校准动作值得养成习惯发版后挑几个真实样本手动对一遍挂钟差和可见时间两个数。自己开着页面去泡杯水、切出去回条消息再回来上报看这两个数各报了多少。差出来的部分正好就是休眠被算进去的量。这个动作花不了几分钟却能在口径切换时及早发现计时代码有没有按预期工作。一次异常长停留的排查现象某内容页平均浏览时长被报得特别高编辑却反馈文章根本没人看完。根因计时用的是打开到关闭的挂钟差用户早上打开页面、下午才关中间大半天都被算成了停留。排查证据按设备维度拆开时长偏高集中在桌面端长时间不关闭的标签页结合 Page Visibility 回看这些样本的可见时间其实很短。修复方式改为只累计 visibilityState 为 visible 的时间挂钟差仅用于 Session 切割。经验看到异常长的停留先问用户是不是开着页面去干别的了。挂钟时长和真实观看时长从来不是一回事。常见疑问浏览器休眠时页面计时器还在正常走吗不完全在。现代浏览器会对后台标签页的 setTimeout 等计时器做限流配合 Page Visibility 事件可以知道页面何时被隐藏。也就是说挂钟时间在走可页面里跑着的计时回调未必按原频率触发。用户锁屏去吃饭这段时间算停留时长吗取决于计时口径。如果按打开页面到关闭页面的挂钟差计算这段锁屏会被统统算进去时长明显虚高如果按页面真实可见、活跃的累计时间计算锁屏期间应被剔除。两种口径差异很大。Session 超时应该用挂钟还是活跃时间统计访问会话的产品通常用挂钟超时来切割会话。例如 Google Analytics 4 默认在 30 分钟无活动后结束一次会话目的是把隔了很久又回来的访问记成两次而不是真的去算用户一直在不在看。为什么平均浏览时长数据忽高忽低常见原因是休眠与后台限流没有被正确处理。挂钟口径下用户开着页面去午休会把时长拉高而不同设备、不同浏览器的后台限流策略又不一样跨环境汇总起来曲线就会抖动。数据来源MDN Web Docs《页面可见性 API》W3C《Page Visibility (Second Edition)》Google《[GA4] Session》会话默认 30 分钟无活动后超时对应工具官网网站分析与指标口径说明总结Session 超时与浏览器休眠的计时校验核心是把两本账分开算Session 切割用挂钟、容忍空白目的是区分一次还是两次访问GA4 默认 30 分钟无活动切会话停留时长用可见时间累加、剔除休眠目的是贴近用户真正看了多久。沿访问时间线把 visible、hidden、切割三个节点标出来再用单调的 performance.now() 只累计可见时段时长数据才不会被午休和锁屏虚高。