结论:grid-template-rows: repeat(n, 100vh) 错误,因容器总高达n×100vh加gap溢出,无法滚动对齐;正确做法是子项设height: 100vh+scroll-snap-align,容器用scroll-snap-type: y mandatory+overflow-y: scroll,并清除body margin。

直接说结论:用 grid-template-rows: repeat(n, 100vh) 做全屏滚动布局是错的,会触发滚动条、内容错位、响应失效,且无法真正“滚动”到每屏。
为什么 grid-template-rows: repeat(n, 100vh) 不适合全屏滚动
这个写法看似“一屏一行”,但实际把整个 Grid 容器高度撑成了 n × 100vh,浏览器会把它当做一个超长单页来渲染,而不是可逐屏滚动的视口容器。更关键的是:
- 它完全忽略
gap—— 加了gap: 20px后,总高度变成n × 100vh + (n−1) × 20px,必然溢出,滚动条立刻出现 - 所有行都强制占满视口高度,但用户滚动时,
100vh是静态计算值,不随缩放、横屏/竖屏切换重算,导致在 iPad 或折叠屏上严重错位 - 没有留出滚动锚点或
scroll-snap的作用空间,纯靠惯性滚动,停不准、跳帧、iOS 上卡顿明显
真正能滚动的全屏网格该用什么结构
全屏滚动的本质是“每个区块占据一个视口高度,并支持 snap 到对应位置”,不是靠 Grid 行高堆出来。正确做法是让每个子项自己设 height: 100vh,Grid 容器只负责排列和对齐:
- Grid 容器设
display: grid; grid-auto-flow: row;,不写grid-template-rows - 每个直接子元素(即每屏内容)设
height: 100vh; scroll-snap-align: start; - 容器加
scroll-snap-type: y mandatory;和overflow-y: scroll; - 必须清除
body { margin: 0 },否则100vh会被默认 margin 撑开,首屏就多出空白
示例结构:
section {
height: 100vh;
scroll-snap-align: start;
}
.scroller {
display: grid;
grid-auto-flow: row;
scroll-snap-type: y mandatory;
overflow-y: scroll;
height: 100vh;
}
如果坚持用 Grid 控制行高,1fr 才是安全替代方案
想用 grid-template-rows 实现视觉上的“分屏”,又不想手动写 100vh,唯一可靠路径是用 fr 单位配合固定容器高度:
- 容器必须设
height: 100vh(不能是100%),且父级无margin/padding/border - 写
grid-template-rows: repeat(n, 1fr)—— 此时每行均分容器可用高度(自动扣除gap) - 若需首屏固定导航栏,改用
grid-template-rows: 60px repeat(n, 1fr),1fr仍能自适应剩余空间 - 注意:这种布局仍是“静态分屏”,不是滚动行为;如需滚动效果,仍得外包一层可滚动容器并启用
scroll-snap
容易被忽略的兼容性雷区
哪怕结构写对了,以下三点仍会导致滚动失效或错位:
-
html或body缺少height: 100%,100vh在某些嵌套场景下会退化为内容高度 - 容器用了
align-items: start或justify-content: start,会让轨道收缩,1fr分不到预期空间 - iOS Safari 15.4 之前版本对
scroll-snap-align支持不稳定,必须加-webkit-scroll-snap-type和-webkit-scroll-snap-align
最稳妥的做法,是放弃用 grid-template-rows 驱动滚动,把滚动逻辑交给原生滚动容器,Grid 只管内部排版 —— 这样既可控,又不会因单位混用埋下隐性 bug。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











