Back

当 100vh 说谎时:修复移动端视口问题

当 100vh 说谎时:修复移动端视口问题

你构建了一个全高度的主视觉区域。在 Chrome DevTools 中看起来完美无缺。但当你在手机上打开时,底部内容被截断了——你花时间精心定位的 CTA 按钮隐藏在浏览器地址栏后面。听起来很熟悉?

这不再只是 Safari 的怪癖。这是视口单位基本定义方式的结果,它同样影响移动端的 Chrome、Firefox 和 Samsung Internet。

核心要点

  • 移动端的 100vh 是根据大视口(工具栏收起状态)计算的,这比用户在初始加载时实际看到的屏幕更高。
  • 使用 100svh 来构建稳定的全高度布局,使其适配工具栏显示时的可见屏幕。
  • 仅在明确希望布局随浏览器 UI 变化而调整大小时使用 100dvh,并接受重排的性能成本。
  • 在全面屏设备上结合使用视口单位和 env(safe-area-inset-bottom),防止内容被系统 UI 遮挡。

为什么移动端的 100vh 会”说谎”

根本原因归结于两个不同的视口概念:

  • 布局视口(Layout viewport):浏览器用于计算 CSS 长度(包括 vh)的视口
  • 视觉视口(Visual viewport):用户在屏幕上实际看到的内容

移动浏览器会刻意保持布局视口固定,即使在滚动时地址栏和导航工具栏收起。这意味着 100vh 是根据展开的工具栏状态——即大视口——计算的,这个值比页面首次加载时的可见屏幕更大。

这不是 bug。 CSS 规范将 vh 定义为等同于 lvh(大视口高度)。浏览器的行为是正确的。只是你的布局没有考虑到这一点。

这就是为什么 DevTools 移动端模拟看起来没问题:它不会模拟动态的浏览器 UI。地址栏在模拟器中永远不会收起。

实际影响

  • 主视觉区域在初始加载时溢出 56–80px
  • 固定底部被导航栏遮挡
  • 全屏模态框底部内容被裁剪
  • 当工具栏在滚动中途收起时布局跳动(如果你使用 dvh)

现代解决方案:svh、dvh 和 lvh

CSS 现在提供了三个明确的视口高度单位,对应不同的工具栏状态:

单位代表最适用于
lvh大视口(工具栏收起)与当前 vh 行为相同
svh小视口(工具栏可见)稳定布局、始终可见的内容
dvh动态视口(当前状态)自适应布局,但会导致重排

对于大多数全高度区域:使用 svh

.hero {
  height: 100vh;     /* 旧版浏览器的回退方案 */
  height: 100svh;    /* 适配加载时的可见屏幕 */
}

100svh 将元素尺寸设置为最小可能视口——意味着即使工具栏完全可见时也能适配。没有溢出,没有裁剪。

当你需要动态调整时:谨慎使用 dvh

.full-height {
  height: 100vh;     /* 回退方案 */
  height: 100dvh;    /* 随工具栏收起而调整大小 */
}

警告: dvh 会在滚动期间随浏览器 UI 变化而重新计算。这可能导致可见的布局偏移和重绘。它不适合作为大多数主视觉区域或模态框的默认选择——仅在明确希望元素随工具栏状态调整大小时使用。

浏览器支持

svhdvhlvh 在 Chrome 108+、Safari 15.4+ 和 Firefox 101+ 中得到支持。综合来看,这覆盖了全球超过 90% 的用户。对于旧版浏览器,100vh 回退方案可以处理其余情况。

值得了解的边缘情况

安全区域插入值(Safe area insets): 在全面屏设备(iPhone 刘海/灵动岛)上,结合视口单位和 env(safe-area-inset-bottom) 使用,避免内容被系统 UI 遮挡:

.hero {
  height: 100svh;
  padding-bottom: env(safe-area-inset-bottom);
}

屏幕键盘: 虚拟键盘主要影响视觉视口,其与布局视口尺寸的交互在不同浏览器和 WebView 中可能有所不同。这与工具栏驱动的视口单位是分开的,可能需要 JavaScript 处理或使用 VirtualKeyboard API 作为渐进增强。

WebView 和内嵌浏览器: 应用内浏览器(Instagram、LinkedIn)通常具有非标准的视口行为。在真实设备上测试,而不仅仅是系统浏览器。

结论

停止将 100vh 作为移动端全高度布局的默认选择。使用 100svh 来构建需要在加载时适配可见屏幕的稳定区域。仅在动态调整大小是有意为之的情况下使用 100dvh。新的视口单位系列的存在正是因为旧的行为始终是一种妥协——现在你有了工具来明确表达你真正想要的效果。

常见问题

不是所有地方都可以。100svh 给你最小的视口高度,比 100vh 更短。对于应该仅在工具栏收起后填充屏幕的元素,100lvh 或 100dvh 可能更合适。专门在必须在工具栏显示的初始页面加载时完全可见的内容上使用 100svh。

可能会。因为 dvh 会在滚动期间浏览器工具栏收起或展开时重新计算,它会触发布局重新计算和重绘。对于静态的主视觉区域或模态框,这种开销是不必要的。仅在明确希望高度实时跟踪当前可见视口的元素上使用 dvh。

行为各不相同。在标准 iframe 中,视口单位引用 iframe 自己的视口,而不是父页面。在 Instagram 或 Facebook 等应用内浏览器使用的 WebView 中,工具栏行为是非标准的,视口单位计算可能不可预测。始终在用户遇到的特定内嵌环境中的实际设备上测试。

svh、dvh 和 lvh 单位不受虚拟键盘影响。键盘会缩小视觉视口,但不会影响这些单位引用的布局视口。要处理与键盘相关的布局偏移,请查看 VirtualKeyboard API,它为你提供了对布局如何响应键盘可见性变化的编程控制。

Truly understand users experience

See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..

OpenReplay