写过 UI 的人都踩过这个坑——在 iPhone 上写 height: 100vh,每次都多出来一截。今天三个 CSS 单位把这件事彻底修了

写过 UI 的人都踩过这个坑——在 iPhone 上写了 height: 100vh,打开一看底部生生被 Safari 工具栏截掉一截。Chrome DevTools 里明明是满屏,上真机就溢出了。这不是你的 CSS 写错了,是 100vh 这个单位从根上就在说谎。

这破事的原因:桌面浏览器的视口高度是固定的,100vh 就等于窗口高度。但移动端浏览器的工具栏会动态出现和隐藏。Chrome 和 Safari 测 100vh 用的是「最大视口」——也就是工具栏完全收起的那个状态。所以页面加载时工具栏还显示着,真正的可见高度其实比 100vh 小,底部内容就这么被工具栏盖住了。

这破问题折腾了前端圈差不多十年。2022 年之前大家的解法是写一段 JS,监听 resize 动态算 window.innerHeight 再塞给 CSS 变量,既丑又抖。

今天浏览器自己有三个单位把这件事修了:

  • svh(small viewport height):工具栏完全展开时的视口高度,最保守,永远不会溢出
  • lvh(large viewport height):工具栏完全收起时的视口高度,和旧 100vh 行为一样
  • dvh(dynamic viewport height):跟随工具栏状态实时变化,动的时候布局也跟着调

三句话理解选哪个:

min-height: 100svh 适合主视觉区域——加载时工具栏还开着,用 svh 刚好撑满当前可见范围,不会有底部被裁的问题。height: 100lvh 适合背景图这类需要撑到最满的场景。height: 100dvh 适合希望元素始终「填满屏幕」的情况,但要接受滚动时高度会微抖。

真实项目里最常见的写法是这样的:

/* 旧写法——移动端炸了 */
.hero {
  height: 100vh;
}

/* 新写法——渐进增强 */
.hero {
  height: 100vh;           /* 兜底旧浏览器 */
  height: 100dvh;          /* 大多数场景用这个 */
}

/* 需要精确控制时明确指定 svh */
.fullscreen-modal {
  min-height: 100svh;       /* 保证内容不被工具栏遮挡 */
  padding-bottom: env(safe-area-inset-bottom);  /* iPhone 适配 */
}

还需要注意一个坑:dvh 在滚动过程中会随工具栏状态重新计算高度,如果你的元素用了 position: fixed 且高度设为 100dvh,滚动时元素高度会抖。对于 fixed 定位的底部导航或者模态框,用 svhdvh 更稳。

浏览器支持情况:Chrome 108+、Safari 15.4+(含 iOS)、Firefox 101+、Edge 108+。综合覆盖率已经超过 90%,国内用户主流机型全部覆盖。旧浏览器直接走 100vh 回退,渐进增强无压力。

三步落地:

第一步,把项目里所有 height: 100vh 换成 height: 100dvh,这是最大覆盖面。

第二步,排查 fixed 定位的底部元素——模态框、底部导航、固定按钮这些,换成 min-height: 100svh 防止抖动。

第三步,结合 env(safe-area-inset-*) 处理 iPhone 刘海和 Home Bar,这两行 CSS 解决的不只是视口单位的问题,还包括虚拟键盘弹出时的安全区。

移动端全屏布局这个坑,从今天起可以彻底从 checklist 里划掉了。

评论区

0 条评论

登录后可评论。