如何让 Grid 布局真正“撑满屏幕”?你踩过的vh高度坑,都在这里了
最近在重构一个后台管理系统时,我再次被一个看似简单的问题卡住了:为什么我的页面明明设置了100vh,却还是出现了垂直滚动条?
更离谱的是,在 iPhone 上打开,页面底部竟然被浏览器工具栏“吃掉”了一截——用户根本看不到页脚。这显然不是设计想要的效果。
翻遍文档、查遍社区后才发现,问题的根源不在 Grid,也不在 CSS 语法错误,而在于我们对vh这个单位的理解太理想化了。尤其是在多行 Grid 布局中,一旦涉及高度分配,稍有不慎就会掉进各种“视觉陷阱”。
今天,我就结合实战经验,彻底讲清楚多行 Grid 中如何用好视口高度单位,帮你避开那些年我们都踩过的坑。
你以为的100vh,真的等于“整个屏幕”吗?
先来泼一盆冷水:
100vh并不总是等于用户实际可见的高度。
听起来反直觉,但这是事实。尤其在移动端,Safari、Chrome for Android 等浏览器会把地址栏、底部导航栏的高度也算进window.innerHeight。也就是说:
height: 100vh;这段代码在某些设备上,实际上比你能看到的内容区域还要高。结果就是页面轻微溢出,触发不必要的滚动条,甚至导致固定定位元素错位。
那怎么办?用dvh!
现代浏览器引入了一个新单位:dvh(dynamic viewport height),它能动态排除浏览器 UI 的影响,真正反映用户可交互区域的高度。
.page-layout { height: 100dvh; /* 推荐替代 100vh */ }支持情况如下:
- ✅ Chrome 79+
- ✅ Safari 15.4+
- ✅ Edge 105+
- ❌ IE 全系不支持(当然现在谁还在乎呢)
如果你需要兼容老版本浏览器,别急,后面我会给出降级方案。
Grid 行高为啥没按比例分?因为你漏了这个前提
我们经常这样写:
.container { display: grid; grid-template-rows: 1fr 2fr 1fr; }期望三行按 1:2:1 分配高度。但现实往往是:前三行紧紧贴在一起,根本没有拉伸。
原因很简单:fr单位只作用于“剩余空间”。而“剩余空间”的前提是容器本身要有明确的高度。
换句话说:
🔴
fr不是魔法,它不能凭空创造高度。
🟢 它只是在一个已知大小的盒子里,把剩下的地方按比例切分。
所以正确的做法是:
.page-grid { height: 100dvh; /* 必须设置容器高度 */ display: grid; grid-template-rows: 1fr 2fr 1fr; }只有当.page-grid明确知道自己有多高时,Grid 才知道怎么把这高度分给三行。
实战案例:构建一个真正全高的页面结构
来看一个典型需求:做一个带头部、主体和底部的管理后台,要求:
- 头部至少 60px,内容多时可自适应
- 底部至少 80px,同样可扩展
- 主体区域占满中间所有剩余空间
- 整体不出现非预期滚动
我们可以这样实现:
.page-grid { height: 100dvh; display: grid; grid-template-rows: minmax(60px, auto) /* header 最小60px */ 1fr /* main 自动填充 */ minmax(80px, auto); /* footer 最小80px */ gap: 1rem; } header { grid-row: 1; } main { grid-row: 2; overflow-y: auto; } footer { grid-row: 3; }关键点解析:
minmax(60px, auto)
意思是:“至少给我 60px,但如果内容更高,就让我自动增长”。这比写死60px更灵活。1fr给主体
它拿到的是“总高度 - 头部高度 - 底部高度 - 两个 gap”的剩余空间。完全无需计算具体数值。gap: 1rem会被自动扣除
Grid 规范规定,gap是从容器内部扣除的,不会额外增加总高度,非常友好。main加overflow-y: auto
当内容太多时,主体区域自己滚动,不影响整体布局结构。
这套模式已经被我用在多个项目中,稳定可靠。
移动端适配:那些必须注意的细节
即使用了dvh,也不能高枕无忧。不同设备的行为差异仍然存在。
坑点1:iPhone 安全区(Safe Area)
全面屏手机(如 iPhone X 及以上)底部有个“小黑条”,如果不处理,你的页脚可能会被挡住。
解决办法是使用环境变量:
body { padding-bottom: env(safe-area-inset-bottom); }或者直接整合到 Grid 布局中:
.page-grid { height: 100dvh; padding-bottom: env(safe-area-inset-bottom); }这样系统会自动留出安全距离。
坑点2:老浏览器不支持dvh怎么办?
可以用 JavaScript 动态计算并注入 CSS 变量作为降级方案:
function setVH() { const vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); } // 初始化 + resize 监听 setVH(); window.addEventListener('resize', setVH);然后在 CSS 中使用:
.page-grid { height: calc(var(--vh, 1vh) * 100); }解释一下:
- 如果浏览器支持dvh,可以直接用;
- 不支持的话,通过 JS 提供的--vh变量模拟真实可用高度;
-1vh作为兜底值,确保基本功能可用。
这个技巧已经在生产环境中验证过,效果接近原生dvh。
坑点3:字体放大或内容过多导致溢出
有时候设计师忘了考虑极端情况:比如用户把浏览器字体调到最大,或者某个弹窗文案特别长。
这时即使用了minmax(auto),也可能因为整体高度受限而导致内容被裁剪。
建议策略:
- 对可能大量内容的区域启用局部滚动(如main { overflow-y: auto })
- 使用clamp()控制字体响应式范围:
main { font-size: clamp(14px, 4vw, 18px); }避免在窄屏上文字过大破坏布局。
高阶技巧:结合媒体查询优化小屏体验
当视口高度非常小时(比如折叠态手机或竖屏 iPad),强行等分布局会让每行都变得太挤。
可以通过媒体查询调整结构:
@media (max-height: 500px) { .page-grid { grid-template-rows: auto 1fr auto; font-size: 0.9rem; gap: 0.5rem; } }在这种情况下,适当缩小间距、降低字号,优先保证可读性和操作空间。
你也可以进一步隐藏非关键元素:
@media (max-height: 400px) { header .logo-text { display: none; } }用户体验远比“完美布局”重要。
写在最后:别再盲目用100vh了
总结一下我在实践中提炼出的核心原则:
| 原则 | 说明 |
|---|---|
✅ 优先使用100dvh替代100vh | 更准确反映用户可见区域 |
| ✅ 必须为 Grid 容器设置显式高度 | 否则fr无效 |
✅ 合理使用minmax()控制最小尺寸 | 防止内容挤压 |
✅ 为主内容区添加overflow-y: auto | 避免全局滚动 |
| ✅ 考虑安全区域和字体缩放 | 提升边缘场景体验 |
| ✅ 提供 JS 降级方案 | 兼容旧浏览器 |
这些经验不是来自规范文档的第一行,而是从一次次线上 bug 和用户反馈中打磨出来的。
下次当你想写height: 100vh的时候,请停下来问一句:我的用户真的能看到这完整的 100% 吗?
如果答案不确定,那就试试dvh+safe-area+JS fallback的组合拳吧。
你用过哪些巧妙的 Grid 高度控制技巧?欢迎在评论区分享你的实战心得。