
做前端这些年我见过最多的布局翻车现场不是 flex 不会用而是明明用了 Grid却只写了display: grid和grid-template-columns两行就宣告完工。Grid 最值钱的部分——容器对整个网格体系的控制力——全被浪费了。这篇文章就围绕 Grid 布局的容器层展开把容器到底能干什么、每个属性背后是怎么运作的、以及我在真实项目里踩过的坑一次性讲清楚。适合已经会写简单栅格、但想系统理解容器能力的朋友也适合那些被 flex 嵌套折磨得不耐烦准备真正转入 Grid 阵营的开发者。1. 声明容器之后display: grid 触发的一连串默认行为很多教程只告诉你给父元素加display: grid就变成了网格容器但没人告诉你这句声明背后浏览器到底替你做了哪些决定。搞清楚这些默认行为后面调样式就不会觉得 Grid 不听话。1.1 一维与二维的本质差异先回到最基础的问题为什么要用 Grid因为你手里的布局需求大多数时候是二维的。一行里排四个卡片、卡片里再分上下区这种行列同时存在的结构用 flex 做必须不断嵌套 flex 容器外层管横向、内层管纵向改一处结构就要跟着改 DOM。而 Grid 的容器天然就同时持有行和列两条轴容器一声明子元素就被放进一个虚拟的表格坐标系里不需要你再额外告诉它你横着排还是竖着排。这里有个很容易误解的点很多人以为display: grid之后子元素会自动变成一行一列排好的漂亮网格。不是的。如果不写任何轨道属性容器只有一个隐式的行轨道子元素会像块级元素一样老老实实从上往下堆叠每个子元素占满整行。也就是说display: grid只是建立了一个网格格式化上下文真正的布局效果要靠轨道属性去驱动。1.2 grid 与 inline-grid容器自身的显示级别除了display: grid还有一个display: inline-grid。两者的区别跟block和inline-block的区别如出一辙普通grid容器本身是块级盒子会占满父级一整行inline-grid容器则像一个行内块宽度由内容撑开可以和别的元素在同一行里共处。容器类型自身盒子行为典型使用场景display: grid占满父容器宽度换行页面主布局、卡片列表display: inline-grid宽度自适应内容同行共存小工具条、按钮组、文字中的内联网格我印象里踩过一次坑把分页组件做成了inline-grid结果父容器宽度不够的时候整个分页组件宁可溢出也不换行找了半天才发现是因为inline-grid的宽度由内容决定没有给它设置width: 100%或max-width。所以用inline-grid时一定要顺手把宽度约束写上。1.3 容器、项目与区域先分清三个角色Grid 和 flex 最不同的一点是Grid 容器里除了项目直接子元素之外还存在一个区域的概念。区域是一块由行列线围出的矩形空间它不属于某个具体子元素而是被命名后由子元素主动去占据。这就带来一个思维转变在 flex 里你是在给元素安排顺序和尺寸在 Grid 里你是在先划好格子再让元素认领格子。这个转变对后面理解grid-template-areas特别重要先记住一句话Grid 容器是画布轨道是坐标轴区域是命名的格子项目是你的内容。2. 轨道尺寸grid-template-columns 与 rows 的三层控制逻辑轨道Track是 Grid 布局最核心的度量单位它对应着容器里的一行或一列。grid-template-columns定义列轨道grid-template-rows定义行轨道。这一节重点讲怎么写轨道尺寸以及每个单位背后的取舍逻辑。2.1 fr 不是 flex: 1它是剩余空间的比例分配器frfraction分数是 Grid 里最常用的单位很多人把它理解成 flex 里的flex: 1其实两者在计算时机上有微妙差别。fr分配的是容器剩余空间也就是说先计算固定尺寸轨道占掉的空间剩下的才按比例分给各fr轨道。举个实际例子.container { display: grid; grid-template-columns: 200px 1fr 2fr; width: 900px; }这段代码里第一列占了 200px剩余 700px 按 1:2 分配第二列约 233.33px第三列约 466.67px。而 flex 的flex: 1会在内容最小宽度基础上做弹性伸缩如果某个子元素里有长单词或图片最小内容宽度会撑大该 flex 项挤压其他项。fr在这方面更强硬一些默认不会让某个轨道因内容而超过分配比例。2.2 minmax 与 repeat最常用的组合拳minmax()函数是轨道尺寸的保险丝它定义了一个最小值和最大值区间让轨道在这个范围内自适应。比如grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));这句是响应式栅格的经典写法。repeat(auto-fill, ...)表示尽可能多地创建列每列的最小宽度 200px最大宽度 1fr也就是均分剩余空间。容器越宽列数越多容器越窄列数自动减少但每列不会低于 200px。这里要注意auto-fill和auto-fit的区别。两者都用于自动填充轨道但在容器宽度不足以再塞下一列时auto-fill会保留空轨道auto-fit会把空轨道折叠为零让现有项目自动撑满。我做过一个数据看板图表卡片用auto-fill在超宽屏上右半侧留下一片空白改成auto-fit后卡片自适应填满了视觉效果差别很大。2.3 1fr 与 auto 的隐性差异最小内容宽度的坑很多人以为1fr和auto都是自适应可以互换实际上它们的行为差异大到你怀疑人生。1fr轨道宽度由分配到的份额决定默认受制于内容最小尺寸。auto轨道宽度由该轨道内所有项目的内容最大宽度决定内容多轨道就宽。实际开发中最常见的 bug 是把grid-template-columns: 1fr 1fr用于左右两栏左栏是一段很长的英文 URL 字符串结果右栏被挤到只剩一点宽度。原因是1fr的最小尺寸默认是auto长字符串的最小内容宽度撑大了左栏轨道。解决方案是给左栏子元素设置min-width: 0或者直接在轨道上写minmax(0, 1fr)grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);多出来的minmax(0, 1fr)相当于告诉浏览器这个轨道的最小值归零内容溢出就溢出轨道宽度必须按比例分配。我在做聊天界面时左侧联系人列表和右侧聊天窗口就遇到过这种问题加了minmax(0, 1fr)之后右侧消息区终于不会被超长用户名挤没了。3. 间距与区域控制格子之间和格子的名字轨道本身只负责定位真正让网格看起来舒服的是间距让代码可读性飞跃的是命名区域。这两样东西都属于容器属性却往往是最容易被忽略的细节。3.1 gap 属性间距的正确写法现在的 Grid 布局推荐使用gap属性来设置间距它同时作用于行方向和列方向gap: 20px行和列间距都是 20pxgap: 20px 30px行间距 20px列间距 30px对比一下传统margin方案用 margin 给网格项目加间距会出现边缘双重间距、换行后间距错乱等一系列问题。gap只在轨道之间插入空间不会在容器边缘额外增加间距这是它最大的优势。不过要提醒一句gap会占用容器的总可用空间。如果你在width: 900px的容器里设置了gap: 30px然后写grid-template-columns: 1fr 1fr两列实际可用空间是 900 - 30 870px每列各 435px。计算轨道比例时心里要时刻记着先减掉 gap否则会困惑明明设了 1fr 1fr为什么每列不是 450px。3.2 grid-template-areas让布局代码像设计稿一样直观grid-template-areas是容器层最有特色的属性它允许你用字符串直接把网格画出来。比如一个经典的圣杯布局.container { display: grid; grid-template-columns: 200px 1fr 200px; grid-template-rows: auto 1fr auto; grid-template-areas: header header header nav main aside footer footer footer; height: 100vh; }每个字符串代表一行每个名字代表该位置对应的区域。写法有几点硬性规定每一行字符串中的列数必须与grid-template-columns定义的列数一致每个单元格可以是一个区域名也可以是一个或多个.表示空单元格同一个区域必须形成矩形不能出现 L 形或不规则形状区域名使用字母、数字和连字符但不能以数字开头写好后子元素只要声明grid-area: header或grid-area: main就能自动占据对应区域完全不需要计算行号列号。我在做管理后台的框架页时用这个方案维护头、侧栏、内容区、底栏的布局后来改版只需要调整grid-template-areas字符串子元素一动都不用动维护体验比改 float 时代好太多。3.3 区域命名的隐性福利自动生成的行列线名称使用grid-template-areas时除了显式的grid-area名称浏览器还会自动为每个区域生成一对行列线名称区域名-start和区域名-end。这意味着你可以在grid-row和grid-column里直接用这些名字定位项目甚至可以混用命名行列线和数字行列线。这个特性在调整某个特殊项目位置时特别好用。我遇到过一种情况主内容区要跨两个区域但又要保持命名区域的稳定。用数字写很容易在改版后算错用main-start这种语义化名字就清晰得多。4. 容器内的对齐justify 与 align 的坐标系终于能分清了Grid 容器和 flex 容器一样有justify-content、align-content、justify-items、align-items但很多从 flex 转过来的人会被绕晕。其实只需要问自己一个问题我要对齐的是轨道这些整体还是项目在这些格子里的位置4.1 content 系与 items 系的真正区别先明确坐标系在 Grid 里justify控制水平方向主轴align控制垂直方向交叉轴。这和 flex 里的默认方向没有区别但 Grid 是二维的所以两组属性都可用。justify-content/align-content对齐的是整个轨道系统。当所有轨道的总宽度小于容器宽度时轨道作为一个整体在容器内如何分布。常用的值有start、center、space-between、space-around、space-evenly。justify-items/align-items对齐的是项目在各自网格区域内的位置。比如某个项目所在的格子是 300px 宽项目本身只有 100px它在格子里靠左还是靠右居中由justify-items决定。默认是stretch也就是拉伸填满格子。属性对齐对象生效前提常见取值justify-content轨道整体水平表格总宽 容器宽start,center,space-betweenalign-content轨道整体垂直表格总高 容器高start,center,space-betweenjustify-items项目在格内水平项目宽 格子宽start,center,stretchalign-items项目在格内垂直项目高 格子高start,center,stretch我在初学时经常搞混的一个场景是想让整个网格居中写在容器上的是justify-content: center还是justify-items: center判断方法就是先问自己轨道总宽有没有占满容器。如果占满了justify-content根本不生效因为根本没有剩余空间可分这时候项目在格内不居中就得用justify-items。4.2 一个居中布局的实操对比来看一个实际例子容器宽 800px一个轨道grid-template-columns: 200px一个项目放在其中。.container { display: grid; grid-template-columns: 200px; justify-content: center; /* 轨道左移不轨道居中 */ }因为轨道总宽 200px 小于 800pxjustify-content: center会把整个轨道推至水平居中。如果我把轨道改成1fr让它撑满容器justify-content就彻底不管用了这时候项目要居中只能靠justify-items或项目自身的justify-self。这个区别在做卡片内部布局时非常关键。我做过一个商品卡组件卡片本身是 grid 容器内部图片、标题、价格三个项目要垂直居中但卡片的高度又由内容撑开。我把align-items: center写在卡片上结果所有项目挤在中间完全不是我想要的效果。后来才反应过来卡片高度刚好被内容撑满时align-content和align-items都不会有视觉变化必须给卡片一个确定高度或让内容有富余空间对齐属性才看得见。5. 隐式轨道与 grid-auto-flow内容溢出声明网格时的兜底机制Grid 容器最让人困惑的一点是明明只声明了 3 列内容却有 5 个多出来的项目去哪了答案是它们进入了隐式轨道——由浏览器自动生成的额外轨道用来安放超出声明范围的网格项。5.1 隐式轨道是怎么产生的当我们写grid-template-columns: 1fr 1fr 1fr容器就有了 3 条列轨道。如果直接子元素有 5 个浏览器会自动在右侧补充第 4、第 5 列轨道这些补充的轨道就叫隐式轨道。隐式轨道默认宽度是auto也就是由内容决定。关键在于你无法用grid-template-columns控制隐式轨道的尺寸因为后者只作用于显式轨道。必须使用grid-auto-columns和grid-auto-rows来给隐式轨道设定统一尺寸。5.2 grid-auto-rows 的典型用法数据卡片列表我在做文件管理列表时文件卡片数量不固定但每个卡片高度必须一致。写法如下.container { display: grid; grid-template-columns: repeat(3, 1fr); grid-auto-rows: 160px; /* 所有隐式行轨道固定 160px 高 */ gap: 16px; }这里只显式声明了列轨道行轨道全部靠grid-auto-rows生成每一行固定 160px。如果文件卡片内容超出就配合overflow: hidden截断或内部滚动。这种模式比给每个卡片设置固定高度稳定得多因为行轨道是容器层面统一控制的不会出现某个卡片高度不一致导致网格行高度参差的情况。5.3 grid-auto-flow: dense 与项目排序的魔法grid-auto-flow控制的是自动放置项目的方向可以理解为项目在没有显式指定位置时的排队方向。grid-auto-flow: row默认按行排列优先把一行填满再换下一行grid-auto-flow: column按列排列优先把一列填满再换下一列grid-auto-flow: dense允许后面的项目回填前面的空位尽量紧凑排列dense这个值特别有意思它适用于项目长短不一但又要最大化利用空间的场景典型就是瀑布流卡片或图片墙。比如你有一些高矮不一的卡片默认排序会出现前面一个高卡片占据两行导致右侧出现空洞。加上grid-auto-flow: dense后浏览器会允许后来的矮卡片回填到空洞里整体紧凑很多。不过要留意dense会改变项目的视觉顺序让它们不再按照 DOM 顺序排列。如果页面对键盘 Tab 焦点顺序有要求比如表单用dense会让焦点跳转顺序变得混乱。我一般在纯展示类的图片墙、卡片墙上用dense在可交互表单里坚决不用。5.4 auto-flow 与密集排列的配合grid-auto-flow还可以和grid-template-columns配合实现自动密集的流式布局。例如相册墙.gallery { display: grid; grid-template-columns: repeat(4, 1fr); grid-auto-rows: 120px; grid-auto-flow: row dense; gap: 8px; }这样设置后图片项目按行排列矮图片可以自动回填高图片旁边的空隙。我这里补充一个从实践中得出的经验密集回填在数据量较大、每个项目尺寸差异明显的场景效果最明显如果所有项目尺寸都一样dense没有任何存在价值反而白白增加浏览器计算量。6. 从设计稿到容器代码一套完整配置的转换过程理论讲了这么多最后用一个接近真实需求的例子把容器层所有关键属性串一遍。假设我们要做一个资讯类页面主体区域结构是左侧分类导航栏固定 220px中间内容区自适应右侧热门推荐固定 300px整体间距 24px并要求在窄屏下能自动换行为单列。6.1 需求拆解与属性选择这种左中右、两翼固定中间弹性的布局是 Grid 容器最擅长的经典场景不需要任何嵌套一个容器搞定.layout { display: grid; grid-template-columns: 220px minmax(0, 1fr) 300px; gap: 24px; align-items: start; max-width: 1440px; margin: 0 auto; padding: 24px; }这里几个选择都是有理由的中间用minmax(0, 1fr)而不是1fr是为了防止内容区里的超长文本把中间列撑大挤压右侧推荐栏。这种问题在资讯页里出现概率极高因为标题和正文里难免有长 URL 或连续英文。align-items: start让三个项目的高度各自独立而不是默认stretch拉成一样高。资讯页的左右栏往往比中间内容短如果拉伸成等高右边会留下大片空白视觉上很难看。max-width加margin: 0 auto是在容器外部做的水平居中和 Grid 本身无关但在真实项目里几乎每次都要写。6.2 响应式降级方案桌面端成形了还要考虑平板和手机。窄屏时应该让三个区域从上到下垂直堆叠此时可以用媒体查询重新定义容器轨道media (max-width: 1024px) { .layout { grid-template-columns: 1fr; } }单列后原来的左右两栏自动变成一行一行往下排。这里有个细节单列时align-items: start依然有效不需要改动。因为 Grid 项目的排列顺序默认按 DOM 顺序左导航、内容、右推荐依次显示正好符合移动端的阅读顺序。6.3 实践中遇到的三个高频问题这套配置我在多个项目里用过下面三个问题出现频率特别高提前写下来帮你绕开。第一个是内容区被挤压的问题。如果不写minmax(0, 1fr)中间列一旦塞进一个white-space: nowrap的提示文字或代码块浏览器为了不溢出会把中间列的最小宽度撑到内容宽度结果右栏被推出容器外。换成minmax(0, 1fr)后内容可以溢出中间列配合overflow: hidden或文本截断即可解决。第二个是间距导致的滚动条。gap: 24px配合min-height: 100vh时如果容器是页面最外层垂直方向的gap会让总高度超过视口出现一条多余的滚动条。解决办法是给容器设置box-sizing: border-box并明确高度约束或者在计算高度时把gap考虑进去。这个问题在 flex 布局里同样存在但 Grid 里因为显式声明了行轨道更容易暴露出来。第三个是子元素里的 grid 嵌套导致的行高崩塌。左侧导航栏本身也是一个 grid 容器里面有几列小图标。如果外层没有align-items: start而左栏内容高度不足内层 grid 容器会被拉伸导致它的子项目按照justify-content: space-between之类的属性分散看起来像导航菜单自己散架了。这里的关键是记牢align-items是容器级属性嵌套的网格容器一旦被外部拉伸内部的布局逻辑完全可能被打破。遇到这种问题优先检查外层的align-items和align-content而不是埋头调内层。7. 容器属性的一页纸速查按我自己的使用频率把容器层属性分为三档供你快速定位优先级属性用途必写display: grid声明网格容器高频grid-template-columns定义列轨道高频gap行列间距高频grid-auto-rows统一隐式行高中频grid-template-areas命名区域提高可读性中频align-items/align-content垂直方向控制低频grid-auto-flow: dense紧凑排列低频grid-template-rows显式行轨道通常配合固定高度场景这个速查表不是标准答案只是从我的项目里归纳出的使用频率。你写自己的布局时可以先从必写 列轨道 间距起步跑通后再逐步加入剩下的属性。就我个人经验而言Grid 容器最容易出问题的地方往往不是某个属性不会写而是没想清楚轨道、区域、项目三者的关系就开始动手。先花两分钟在纸上把行列画出来再落到代码里比写了半小时再回来调要高效得多。这个习惯我保持了几年每次复杂布局都没翻过车。