Safari 中vh单位的“呼吸式抖动”:一场与视口抽象层的深度对话
你有没有遇到过这样的场景:一个精心设计的全屏轮播页,在 iPhone 上刚加载时严丝合缝,可用户手指一滑——地址栏悄然收起,整个.hero区域突然“吸气式”拉长,文字被撑开、按钮错位、视频封面露出难看的黑边?再往上一拽,地址栏弹回,页面又“呼气式”压缩……这种仿佛网页在自主呼吸的诡异抖动,不是动画没写好,也不是 CSS 写错了,而是 Safari 正在用它自己的方式,重新定义什么叫“视口高度”。
这不是 bug,是 Safari 对「视口」这个词,比其他浏览器更较真了一点。
为什么100vh在 Safari 里会“变胖又变瘦”?
先抛开术语。打开 Safari 开发者工具(连上 macOS 的 Safari,选中 iOS 模拟器或真机),在控制台敲:
getComputedStyle(document.documentElement).height // 输出类似:'789px'然后滚动页面——再敲一遍:
getComputedStyle(document.documentElement).height // 可能变成:'852px'差了整整 63px。这 63px,就是 Safari 地址栏的高度。
关键在于:Safari 的vh不是按你“看到的画面”算的,而是按它内部那个叫 Layout Viewport 的逻辑容器算的。这个容器,会随着浏览器 UI 的展开/收起实时伸缩。而 Chrome 和 Firefox 的vh,更贴近你眼睛看到的区域(Visual Viewport),所以稳如磐石。
你可以把 Layout Viewport 想象成一张可伸缩的桌布,Safari 是个强迫症裁缝——你把碗(地址栏)拿走,它立刻把桌布往下多铺一截;你把碗放回来,它又“唰”地抽走一截。而100vh就是这张桌布的 100%,它当然跟着变。
📌 真实数据不会骗人:iPhone 14 Pro Max 在 Safari 中,
100vh在静止状态约 789px,滚动到底部地址栏完全隐藏后跳到 852px——波动幅度超 8%。这不是边缘 case,是每天数亿次滚动中必然发生的渲染重排。
更讽刺的是,W3C 规范里写的清清楚楚:“vh基于 initial containing block 的高度”,而这个 block 的高度,恰恰由 Layout Viewport 决定