移动端布局的“定”与“变”:为什么vh正在悄悄取代px
你有没有遇到过这样的问题?
一个精心设计的移动端登录页,在 iPhone 上完美居中,可一到安卓机上,底部突然多出一片白;横屏变竖屏时,内容被截断;键盘弹起后,输入框直接“隐身”在软键盘之下……
这些问题背后,往往藏着同一个元凶——我们还在用固定像素(px)去对抗千变万化的屏幕。
而真正的解法,其实藏在一个简单的单位里:vh。
从“画布思维”到“视口思维”:移动布局的认知升级
过去做网页,我们习惯把页面当成一张固定大小的画布。宽度 375px,高度 667px —— 对着设计稿,一个像素一个像素地抠,生怕错位。
但这套逻辑,在移动端早已失效。
今天的用户设备五花八门:
- 同样是 6.1 英寸,iPhone 和安卓旗舰的实际可视高度可能差上百像素;
- 竖屏和横屏切换,视口瞬间翻转;
- 浏览器地址栏、系统状态栏、底部导航条……这些 UI 元素还会动态侵占可用空间。
如果你还执着于“这个盒子必须是 400px 高”,那等于是在波涛汹涌的大海上,试图用一根铁棍撑住一艘船。
真正稳的,是能随水位升降的浮桥。
这就是vh的思维方式:我不关心设备具体多高,我只关心它现在“可见部分”的比例是多少。
1vh = 当前视口高度的 1%
它不是绝对尺寸,而是相对占比。就像水位计,永远跟着水面走。
px并没有错,但它不适合所有角色
别误会,px依然是 CSS 的基石。它的价值在于精确控制,但这也正是它在响应式场景下的软肋。
什么时候该用px?
当你需要“钉死”某个细节时:
.icon { width: 24px; height: 24px; border: 1px solid #ccc; }图标、边框、描线这类微小元素,必须保持清晰和一致性。用rem或em可以更好支持字体缩放,但核心逻辑不变:精细控件,适合固定单位。
什么时候px会坑你?
看这个典型场景:你想做一个全屏背景图。
.full-bg { height: 667px; /* 对应 iPhone 8 */ background: url(...) center/cover; }结果呢?
- 在更小的手机上,内容被裁剪;
- 在更大的屏幕上,底部露出空白;
- 横屏时完全失衡。
为了补救,你不得不写一堆媒体查询:
@media (min-height: 700px) { .full-bg { height: 700px; } } @media (min-height: 800px) { .full-bg { height: 800px; } } /* ... 还要覆盖折叠屏?*/这已经不是开发,是猜谜游戏。
vh的真正威力:让布局自己“长大”
回到刚才的问题,换成vh:
.full-bg { height: 100vh; background: url(...) center/cover; }就这么一行代码,搞定所有设备。
因为它不再依赖“我认为多高”,而是问:“你现在能看到多少?我全占了。”
实战案例:固定头部 + 可滚动内容区
这是移动端最常见的布局模式:顶部导航栏固定,下面内容区域滚动。
传统做法常依赖 JavaScript 动态计算高度:
const headerHeight = 56; const content = document.querySelector('.content'); content.style.height = window.innerHeight - headerHeight + 'px';不仅复杂,还容易在旋转屏幕或键盘弹起时失效。
而用vh,纯 CSS 就能优雅解决:
.content { height: calc(100vh - 56px); overflow-y: auto; }无需 JS,无需监听事件,浏览器自动完成计算。设备一转,尺寸即变。
这才是现代前端该有的样子:声明意图,而非指挥过程。
别高兴太早:100vh在移动端有“坑”
你以为100vh就是完美的全屏?在 iOS Safari 上,它可能会让你翻车。
问题现象
在 iPhone 上打开页面,.container { height: 100vh }的元素底部竟然超出了可视区域,触发了不必要的滚动条。
原因揭秘
iOS Safari 的100vh是按“完整屏幕高度”计算的,包含了浏览器工具栏的高度。当页面加载时,工具栏是隐藏的,所以100vh实际比你看到的“可视高度”还要大。
换句话说:你想要的是“此刻我能看见的地方”,但浏览器给你的却是“理论上整个屏幕那么高”。
解决方案一:CSS 兼容写法
利用 WebKit 特有的-webkit-fill-available:
.container { height: 100vh; height: -webkit-fill-available; height: fill-available; }这个值会真实反映当前可用高度,避开工具栏干扰。
解决方案二:JS 动态注入变量(推荐)
最可靠的方案,是让 JavaScript 获取真实的视口高度,并注入 CSS 变量:
function setVH() { const vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); } // 初始化 + 监听变化(如旋转、键盘弹起) setVH(); window.addEventListener('resize', setVH);然后在 CSS 中使用:
.container { height: calc(var(--vh) * 100); /* 真实 100vh */ }这样无论什么设备、什么浏览器,拿到的都是“此时此刻真正可用的高度”。
如何聪明地搭配px与vh?
它们不是敌人,而是搭档。关键在于分工明确:
| 场景 | 推荐单位 | 理由 |
|---|---|---|
| 全屏容器、主视图高度 | ✅vh | 自适应视口,免去媒体查询 |
| 内容卡片、模态框最大高度 | ✅min-height: 80vh | 弹性伸缩,避免过大 |
| 固定组件(按钮、图标) | ✅px/rem | 保证视觉精度和一致性 |
| 文字大小 | ✅rem/em | 支持用户缩放,更无障碍友好 |
| 外边距、内边距 | ✅px或% | 小范围间距更适合固定值 |
还可以结合现代 CSS 函数,打造“智能尺寸”:
.card { min-height: clamp(300px, 80vh, 600px); }解释一下这行代码的智慧:
- 最小不能低于 300px(防止在极小屏上压缩过度);
- 最大不超过 600px(避免在平板或桌面端过高);
- 中间尽可能占据 80% 视口高度。
这种“有边界的弹性”,才是真正成熟的响应式设计。
调试建议:别被 DevTools 欺骗
你在 Chrome 开发者工具里调试100vh,一切正常。可一到真机,问题就来了。
原因很简单:PC 浏览器没有地址栏收起行为,也没有软键盘。
所以记住:
所有关乎视口高度的样式,必须在真实移动设备上测试!
尤其是涉及vh的布局,模拟器只能参考,不能信任。
写在最后:从“适配”到“顺应”
我们曾经努力让网页“适配”各种设备,写无数媒体查询,监听各种事件。
但现在更好的方式是:让布局本身具备顺应能力。
vh就是这种思维转变的起点。
它不承诺“在每台设备上看起来一模一样”,而是确保“在每台设备上都恰到好处”。
这就像园艺师不会强行掰弯树枝来统一造型,而是修剪引导,让它自然生长成最适合环境的样子。
下次当你又要写height: 400px的时候,不妨停下来问一句:
“我真的需要一个固定的数字吗?还是说,我可以交给
vh来处理?”
也许那一行height: 100vh,就能帮你省下 50 行 JS 和 3 个 bug 报告。
毕竟,最好的代码,是不需要写的代码。