告别移动端 100vh 困境:CSS 动态视口单位终极解决方案

https://juejin.cn/post/7520548278338322483
意外富翁 · 8个月前 · 技术 · 58 · 0

作为一名前端开发者,我们经常会遇到需要实现占满整个屏幕的欢迎页、弹窗蒙层或者 fixed 定位的底部菜单等需求。

直觉告诉我们,这很简单,给它一个 height: 100vh 就行了。

.fullscreen-element {
    height: 100vh;
    width: 100%;
    color: #000;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 10em;
    background-color: #fff;
}

在 PC 端预览,完美!然而,当你在手机上打开时,可能会看到超出屏幕高度,出现滚动条的场景。

明明是 100vh,为什么会超出屏幕高度?这个烦人的滚动条到底从何而来?

如果你也曾为此抓耳挠腮,那么恭喜你,这篇文章就是你的 "终极答案"。今天,我将带你彻底搞懂 100vh 在移动端的 "坑",并为你介绍当下最完美的解决方案。

1. 问题根源:移动端动态变化的 "视口"

要理解问题的本质,我们首先要明白 vh (Viewport Height) 单位的定义:1vh 等于视口高度的 1%。

在 PC 端,浏览器窗口大小是相对固定的,所以 100vh 就是浏览器窗口的可见高度,这没有问题。

但在移动端,情况变得复杂了。为了在有限的屏幕空间里提供更好的浏览体验,手机浏览器(尤其是 Safari 和 Chrome)的地址栏和底部工具栏是动态变化的。

  • 初始状态:当你刚进入页面时,地址栏和工具栏是完全显示的。
  • 滚动时:当你向下滚动页面,这些 UI 元素会自动收缩,甚至隐藏,以腾出更多空间展示网页内容。

关键点来了:大多数移动端浏览器将 100vh 定义为 "最大视口高度",也就是当地址栏和工具栏完全收起时的高度。

这就导致了:在页面初始加载、地址栏还未收起时,100vh 的实际计算高度 > 屏幕当前可见区域的高度。

于是,那个恼人的滚动条就出现了。

2. "过去式" 的解决方案:JavaScript 动态计算

在很长一段时间里,前端开发者们只能求助于 JavaScript 来解决这个问题。思路很简单:通过 window.innerHeight 获取当前可见视口的高度,然后用它来动态设置元素的 height。

function setRealVH() {
  const vh = window.innerHeight * 0.01;
  document.documentElement.style.setProperty('--vh', `${vh}px`);
}
// 初始加载时设置
window.addEventListener('load', setRealVH);
// 窗口大小改变或旋转屏幕时重新设置
window.addEventListener('resize', setRealVH);

然后在 CSS 中这样使用:

.fullscreen-element {
  height: calc(var(--vh, 1vh) * 100);
}

这个方案的缺点显而易见:

  • 性能开销:监听 resize 事件过于频繁,可能会引发性能问题。
  • 逻辑耦合:纯粹的样式问题却需要 JS 来解决,不够优雅。
  • 时机问题:执行时机需要精确控制,否则可能出现闪烁。

虽然能解决问题,但这绝不是我们想要的 "终极方案"。

3. "现在时" 的终极解决方案:CSS 动态视口单位

谢天谢地,CSS 工作组听到了我们的呼声!为了解决这个老大难问题,CSS Values and Units Module Level 4 引入了一套全新的动态视口单位。

它们就是我们今天的 "主角":

  • svh (Small Viewport Height): 最小视口高度。对应于地址栏和工具栏完全展开时的可见高度。
  • lvh (Large Viewport Height): 最大视口高度。对应于地址栏和工具栏完全收起时的高度(这其实就等同于旧的 100vh)。
  • dvh (Dynamic Viewport Height): 动态视口高度。这是最智能、最实用的单位!它的值会随着浏览器 UI 元素(地址栏)的出现和消失而动态改变。

所以,我们的终极解决方案就是:

.fullscreen-element {
  height: 100svh; /* 如果你希望高度固定,且永远不被遮挡 */
  /* 或者,也是我最推荐的 */
  height: 100dvh; /* 如果你希望元素能动态地撑满整个可见区域 */
}

使用 100dvh,当地址栏收起时,元素高度会平滑地增加以填满屏幕;当地址栏滑出时,元素高度又会平滑地减小。整个过程如丝般顺滑,没有任何滚动条,完美!

浏览器兼容性

你可能会担心兼容性问题。好消息是,从 2023 年开始,所有主流现代浏览器(Safari, Chrome, Edge, Firefox)都已经支持了这些新的视口单位。

可以看到,兼容性已经非常理想。除非你需要支持非常古老的浏览器版本,否则完全可以放心地在生产环境中使用。

告别 100vh 的时代

让我们来快速回顾一下:

  • 问题:在移动端,100vh 通常被解析为 "最大视口高度",导致在浏览器 UI 未收起时内容溢出。
  • 旧方案:使用 JavaScript 的 window.innerHeight 动态计算,但有性能和维护问题。
  • 终极方案:使用 CSS 新的动态视口单位,尤其是 100dvh,它能根据浏览器 UI 的变化自动调整高度,完美解决问题。

当需要实现移动端全屏布局时,请大胆地告别 100vh,拥抱 100dvh!

已复制到剪贴板

评论 0 条

暂无评论,来种下第一颗种子。