
如果你平时主要用Flexbox排页面第一次打开Grid文档时很容易被一堆新属性劝退。但当我真正把Grid用进项目之后回头再想很多复杂布局其实根本不该用Flex硬撑。Flex擅长一维排列而Grid从底层就把行和列同时存在这件事纳入建模。本文不打算逐条抄MDN属性表而是把我从踩坑到熟练的完整思路整理出来围绕轨道、隐式网格、元素定位、网格区域、对齐和响应式布局这几个核心问题展开。适合已经写过一段时间CSS、想彻底掌握Grid的开发者也适合被Grid很难记劝退的初学者。读完之后你会发现Grid不是又一个需要死记硬背的布局方案而是一套能让你把布局思路直接写进CSS的语法系统。1. 先从本质理解Grid不是Flex的升级版而是另一种布局模型很多人学Grid时第一个问题是它和Flexbox到底哪个厉害我的结论是它俩根本不在同一个维度上。Flexbox是一维布局模型你声明flex-direction: row或column之后主轴上排项目、交叉轴上做对齐。项目换行之后每一行都是独立的弹性上下文上一行的高度不会主动去和下一行对齐因为Flex本来就没打算处理行列交叉关系。Grid则是二维布局模型。容器里同时存在行轨道和列轨道每个网格项位于某个行线和列线之间。你可以在一个维度上用固定尺寸、在另一个维度上用fr或auto让Grid独立计算整块区域。这个差异带来的实际影响是Flex适合内容驱动、方向单一的组件内部布局比如导航栏、按钮组、文字图标一行排开而Grid适合页面级或卡片级的整体骨架排布比如侧边栏主内容区、多行多列的商品列表、报表型界面。需要明确一点Grid和Flex不是互斥关系。我在生产环境里大量使用Grid管整体Flex管内芯的组合方式。例如一个卡片网格用Grid定义行列每个卡片内部用Flex排列标题、描述和按钮。这种组合几乎覆盖了绝大多数业务布局需求。用一句话向新人解释Flex是你手上的一根绳子只要沿着一个方向把东西绑紧就行Grid是一张棋盘每个位置都能被精确指定。如果你只需要横向或纵向的排列顺序用Flex就够了。一旦出现某一行要横跨多个单元格两个模块既要左对齐又要等高这类需求就是Grid发挥价值的时候。另外Grid布局对代码审查和团队协作也很友好。因为轨道定义集中在容器上结构信息一目了然不像某些Float或Flex技巧那样需要反复看子元素的margin和width才能推断出整个布局。这也是后来在重构老项目时我强推Grid的原因。2. 轨道定义与隐式网格理解Grid布局的第一道坎2.1 显式轨道用grid-template-columns和grid-template-rows画格子Grid容器最基础的两个属性是grid-template-columns和grid-template-rows。它们决定显式网格里有几条列、几条行以及每条轨道的尺寸。初次上手时建议先用最简单的三列布局来理解轨道概念.container { display: grid; grid-template-columns: 100px 200px 100px; }这段代码表示容器内部划分出3条列轨道宽度分别100px、200px、100px。容器里的前3个子元素会自动填入这3列超过3个的子元素则会在下一行继续排列。这里就引出一个非常关键的概念——隐式网格。当Grid容器里的子元素数量超过你在grid-template-columns里定义的列数时多余的项目会自动创建新的行轨道。这些自动生成的轨道叫隐式网格。它们的尺寸由grid-auto-rows和grid-auto-columns控制。我见过很多人在跑通基础Demo后突然发现为什么我多放了一个div它的高度和我设的不一样原因就是没有理解隐式网格。.container { display: grid; grid-template-columns: 1fr 1fr 1fr; grid-auto-rows: 120px; }这样设置后显式列定义了3列所有自动换行产生的隐式行都保持120px高整体结构会比较可控。生产环境中我几乎总是会写grid-auto-rows除非有明确理由去依赖内容撑开。2.2 fr、百分比、auto和minmax到底该怎么选Grid引入了fr单位代表剩余空间的一个份数这是很多人愿意用它替代Flex的%或者margin计算的直接原因。但要分清几个单位的适用场景固定尺寸使用px、rem等适合侧边栏、icon等稳定区域百分比适合希望轨道随容器尺寸等比缩放的场景但在与gap同用时百分比基于容器宽度计算容易溢出因为总宽度还包括了gap的距离fr只在分配剩余空间时生效。它不会和gap产生冲突因为它分的是扣除固定轨道和gap之后的剩余空间auto轨道尺寸由内容决定最小是内容最小尺寸最大是内容最大尺寸。需要高由内容撑开时用auto非常合适minmax()给轨道设定最小和最大尺寸例如minmax(200px, 1fr)意思是至少200px最大再分剩余空间。实际项目里自适应三列布局最好用的是grid-template-columns: 240px 1fr 320px;这条声明表示左右两栏固定宽度中间栏占满剩余空间。配合gap属性写间距语义比传统的float方案清晰得多。2.3 gap别再靠margin硬撑间距旧写法里网格项之间的间距只能通过在每个子元素上设置margin实现。Grid直接提供了gap属性用来控制行间距和列间距。例如.container { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px 24px; }第一个值是行间距第二个值是列间距。需要说明的是gap不会改变轨道尺寸的计算方式它在布局计算时被单独扣除所以不用担心百分比加gap出现溢出问题。这也是Grid在布局语义上比Float/Inline-Block方案更合理的地方。3. 定位的三种姿态网格线编号、span和grid-area3.1 网格线编号学会从0开始数的第几条线Grid里每个单元格的边界由网格线组成。一个3列布局其实有4条纵向的网格线。子元素默认按文档顺序自动填充到下一个可用单元格但你也可以用grid-column和grid-row指定它从哪条线开始、到哪条线结束。.item { grid-column: 1 / 3; grid-row: 1 / 2; }上面这个元素从第1条列线到第3条列线占据两列从第1条行线到第2条行线占据一行。这种写法叫line-based定位。对于结构规则、数量稳定的布局这种方式很直观。但值得注意网格线编号从1开始而不是从0开始。如果你要从最后一条线往回数可以使用负数-1代表最后一条线。比如.item { grid-column: 1 / -1; }表示这个元素横跨整行。这个技巧在处理通栏Banner底部全宽栏时非常常用。3.2 span关键字和定位简写除了明确起止线还可以用span表示占据多少个轨道.item { grid-column: 2 / span 3; }表示从第2条线开始向右跨越3个轨道。这种写法的好处是你不必关心最后一条线的编号只需要知道从哪开始跨几条。当轨道数量发生变化时span写法的适配性会更强。grid-area是grid-row-start / grid-column-start / grid-row-end / grid-column-end的简写顺序是行起始 / 列起始 / 行结束 / 列结束不是按人们习惯的先列后行。这个顺序特别容易记错我自己第一次写就被绕了进去。所以除非用命名区域的方式否则我通常直接写grid-row和grid-column。如果你确实想用一句话定位完整区域也可以写作.item { grid-area: 1 / 2 / 3 / 4; }但可读性真的不如分开写清晰。3.3 命名网格线给坐标起名字布局语义化随着布局复杂度上升裸数字的line-based写法会迅速变得难以维护。你可能会碰到昨天还看得懂代码今天要看半天的情况。此时可以用命名网格线来增加语义.container { display: grid; grid-template-columns: [start] 200px [main-start] 1fr [main-end] 200px [end]; } .item { grid-column: main-start / main-end; }中括号里的名字就是这个位置网格线的别名。元素定位时使用别名代码的可读性就上来了。在实际团队项目里命名网格线的价值很高因为它把这条线是侧边栏和内容区的分界这类业务语义直接写进了布局代码。4. grid-template-areas用画字符画的方式规划整页布局4.1 定义区域代码即草图如果你觉得网格线编号还是太抽象那么grid-template-areas可能是最友好的一种方式。它的思路是给网格区域命名然后用一串字符串描述区域排列就像在代码里画了一幅ASCII草图。.container { display: grid; grid-template-columns: 200px 1fr 200px; grid-template-rows: 80px 1fr 60px; grid-template-areas: header header header sidebar content aside footer footer footer; gap: 8px; } .header { grid-area: header; } .sidebar { grid-area: sidebar; } .content { grid-area: content; } .aside { grid-area: aside; } .footer { grid-area: footer; }这段代码对应的布局效果基本就是字符串里显示的样子第一行全是header第二行依次是sidebar、content、aside第三行全是footer。这种所见即所得的写法比Line-based定位更直观。评审代码时哪怕不是专业前端也能看出页面骨架这对我维护老项目帮助很大。使用grid-template-areas时有几个细节必须注意每个字符串的行数必须等于grid-template-rows定义的轨道数每行的列数必须等于grid-template-columns定义的轨道数。如果某一行没有对应元素用点号.占位。例如不希望把某一列填满时可以写grid-template-areas: header header header sidebar content . footer footer footer;这个点号表示该单元格留白不归任何命名区域使用。4.2 区域命名的限制和取舍grid-template-areas虽然直观但也有边界一个命名区域只能构成一个矩形不能是L形或T形。比如左侧大区块跨两行、右上角和右下角各放一个卡片这种不规则的复杂布局用grid-template-areas会很难表达。此时我一般改用line-based定位处理具体模块或者干脆在Grid内部再嵌套Grid。区域名称也会隐式地创建网格线名称。例如header区域会自动产生名为header-start和header-end的网格线这意味着你可以在其他元素的grid-column中使用这些隐式的线名称。这个特性偶尔会带来意外的好处但也可能让命名空间显得混乱因此团队里使用时要约定好命名字段。4.3 响应式重构只改grid-template-areasgrid-template-areas最让我惊喜的地方是响应式重构。通常移动端和桌面端的页面骨架完全不同传统做法是要么改DOM顺序要么用浮层覆盖要么大量重写子元素样式。而Grid方案里你只需要在媒体查询里重写容器上的grid-template-areas与grid-template-columnsHTML结构和子元素的grid-area保持不变。media (max-width: 768px) { .container { grid-template-columns: 1fr; grid-template-areas: header sidebar content aside footer; } }同样的DOM在桌面端是侧边栏内容右侧栏三栏布局在移动端变成单列堆叠关键改动全集中在Grid容器上。这比在子元素上写一堆float、margin和media query要好维护得多。5. 对齐体系justify与align那一堆属性到底谁管谁5.1 items与content的区别项目内部对齐 vs 整体轨道对齐Grid的对齐属性从两个层面起作用。第一层是items级别作用于网格项在它所占据的单元格内部的位置第二层是content级别作用于所有网格轨道作为一个整体在容器中的位置。一旦把这两个层面区分开就再也不会混淆这些属性了。justify-items控制网格项在单元格内沿行轴水平方向的对齐方式默认是stretchalign-items控制网格项在单元格内沿列轴垂直方向的对齐方式默认也是stretchjustify-content当所有网格轨道的总宽度小于容器宽度时控制整体轨道在容器内水平方向的位置align-content当所有轨道总高度小于容器高度时控制整体轨道在容器内垂直方向的位置。举一个典型场景Grid容器高度为100vh但内容只有一行且行的高度只有300px。此时如果你希望这行内容居中显示应该设置align-content: center而不是align-items: center。align-items作用于每个项目在自己的单元格里的位置但单元格本身就是内容高度时它看不出居中效果。很多新手在这里卡住就是没有明确轨道整体和单元格内部两个层次。5.2 self级别单独调整某个网格项有时候你只希望某一个项目不遵守统一的items设置可以使用justify-self和align-self。比如卡片网格里有一个按钮需要固定在右下方其他内容默认拉伸这个需求用justify-self: end和align-self: end就能解决不需要给按钮外面套一层额外的div。5.3 简写属性place-items和place-contentGrid提供了两个高频简写place-items和place-content。语法是水平值 垂直值.container { place-items: center center; place-content: center center; }如果只写一个值则两个方向都用同一个值。所以在项目里我经常用place-items: center实现网格项的快速居中用place-content: center实现整个网格区域的居中。这两个简写在日常开发中能省很多代码。对齐属性在不同浏览器中的表现一致性很好唯一需要注意的是当网格项设置了尺寸但轨道空间不足时stretch不会产生可见效果。这不算bug而是Grid计算中轨道尺寸优先于项目对齐的体现。遇到这类情况时先检查是不是grid-template-columns里的1fr和固定的网格项尺寸产生了冲突再去考虑对齐属性。6. 实测中容易翻车的几个细节fr、auto-fit、隐式网格6.1 min-width: auto导致的溢出问题Grid项目默认的min-width是auto这一点和Flex项目一样。当一个网格项内部是一段很长的URL或一张较大图片时网格项的最小尺寸会由内容的最小尺寸撑大结果就是轨道宽度被内容撑破布局溢出。解决方法是给网格项设置min-width: 0或者给内部图片设置min-width: 0 / max-width: 100%。.card { min-width: 0; }这个坑在图片画廊和表格型布局里很容易遇到。一旦出现横向滚动条但找不到原因时优先检查网格项的min-width。这算是我工作中处理Grid溢出问题的第一大原因。6.2 repeat(auto-fit, minmax(...))与auto-fill的细微差别做响应式网格时很多人喜欢写grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));auto-fit表示当轨道数量超过了物品数量时轨道会被折叠空轨道宽度变为0网格项可以拉伸占据更多空间。auto-fill则不管有没有足够内容都会保留空轨道占位。实际视觉效果是auto-fit让卡片尽量填满一行auto-fill让卡片尽量保持统一尺寸并在右侧留白。我的经验是商品列表、卡片墙这类希望每行尽量放更多卡片的场景用auto-fit内容固定、希望保持所有卡片同宽的报表场景用auto-fill。两者在大多数情况下可能看起来差不多但一旦容器够宽、项目不够填满整行差异会非常明显。6.3 隐式行高和grid-auto-flow: dense的注意点当项目需要被显式放置到网格中时可能会出现空洞。比如第一个元素用了grid-column: 1 / 3占据两列第二个元素较宽需要换行但下一行的第一个格子因为第一个元素延伸下来而被占住就会产生空白。这时可以把grid-auto-flow: dense加上让后续项目按顺序回填空洞。但dense会改变DOM的视觉顺序对于依赖Tab键读屏的场景可能造成理解障碍所以使用前要评估可访问性影响。隐式行高的坑在于如果你只设置了grid-template-columns而不设置grid-auto-rows自动换行的行高默认是auto也就是由内容决定。在卡片布局中这往往导致各行高度参差不齐视觉上很乱。解决办法是给grid-auto-rows设置固定值或者使用minmaxgrid-auto-rows: minmax(120px, auto);这样既保证最小高度又允许内容继续撑开。6.4 固定轨道与1fr同时使用时内容过长怎么办典型的侧边栏主内容区布局中如果侧边栏是固定宽度但侧边栏内部文字很长Grid默认会让侧边栏轨道被内容撑大因为固定像素并不等同于min-width限制。如果要强制侧边栏保持固定宽度就需要在侧边栏元素上设置min-width: 0配合overflow或者使用minmax(200px, auto)这类更宽容的模式。实际项目中我更倾向于grid-template-columns: minmax(0, 240px) minmax(0, 1fr);这样左右两列都不会因为内部内容过长而突破既定比例在长表格、日志展示等场景里尤其稳定。7. 几组可以直接抄作业的实战布局7.1 经典后台管理框架侧边栏顶部栏主内容区后台管理页面最标准的骨架是顶部通栏左侧导航右侧为内容区。用Grid可以一次性完成.admin-layout { display: grid; grid-template-columns: 220px 1fr; grid-template-rows: 64px 1fr; grid-template-areas: sidebar header sidebar content; height: 100vh; } .sidebar { grid-area: sidebar; } .header { grid-area: header; } .content { grid-area: content; overflow: auto; }高度100vh后整个管理后台不依赖body滚动内容区独立滚动。移动端可以调整grid-template-areas为单列结构侧边栏隐藏或改成抽屉改动范围集中在容器样式上。7.2 自适应图片画廊auto-fit minmax图片画廊最省心的写法.gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; } .gallery img { width: 100%; height: 200px; object-fit: cover; }这个方案不需要JavaScript也不需要媒体查询容器宽度变化时Grid会自动决定一行放几张图。前提是给图片设置固定height配合object-fit: cover否则不同图片比例会让行高不一致破坏整齐感。7.3 内容底部对齐的等高卡片Grid一个天然优势是让同一行的网格项高度一致。卡片组件中按钮经常需要固定在卡片底部传统Flex很难优雅处理多行卡片之间的底部对齐。Grid思路如下.card-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; } .card { display: flex; flex-direction: column; } .card .action { margin-top: auto; }外层Grid保证每个卡片所在单元格高度一致内层Flex让按钮区域的margin-top: auto把按钮推到卡片底部。Grid创造等高环境Flex处理内部内容流两者配合顺手。7.4 表单中的标签与输入框对齐表单里两列表单标签一列、输入框一列想做到严格对齐用Grid最直接.form-grid { display: grid; grid-template-columns: 120px 1fr; gap: 12px 16px; align-items: center; }标签右对齐可以给标签加justify-self: end输入框自动占满剩余空间。行数再多也不用担心某个输入框或标签因为内容长度不同而错位。8. 从Grid再往前一步嵌套网格与子网格有时一个单元格内部还需要继续划分网格可以直接把网格项也设为display: grid这就是嵌套网格。嵌套网格是CSS Grid里最简单也最实用的组合方式。外层网格负责整体页面骨架内层网格负责局部区域的内容排列。比如在商品卡片区域外层网格决定每张卡片的位置卡片内部的图片区、标题区、价格区再用一个内层网格做细化排列。而子网格subgrid是用来解决内外网格轨道对齐问题的。嵌套网格默认不会继承外层网格的行列定义所以内外网格间的基线通常对不齐。子网格允许内层网格使用父级网格的轨道定义比如.card { grid-template-rows: subgrid; grid-row: span 3; }这样卡片内部的三行会直接对应父网格的三行轨道从而实现跨卡片的内容对齐。目前现代浏览器对subgrid的支持已经比较成熟但在大型老旧项目里我还是会先确认浏览器支持情况再引入不要因为它看起来很酷就直接上线。网格嵌套使用时需要注意内层网格有自己的gap内外层gap叠加会让间距看起来不一致。遇到视觉间距异常时试着把外层网格和内层网格的gap统一规划不要各自随意设置。这是我做了几个组件库后总结出的实用经验。9. 练手建议用Grid重排几个常见页面如果你想把Grid真正吃透建议不要只看文章而是找几个具体页面亲手排一遍。我比较推荐从这三个开始第一个是博客文章页。包含头部、文章主体、侧边栏、底部用来练习grid-template-areas和响应式重排第二个是图片展示墙。包含大量尺寸不一的图片用来练习auto-fit、minmax、dense和min-width: 0的溢出处理第三个是后台数据报表页。包含固定表头、多列排序筛选区域用来练习固定轨道与1fr的组合、内容过长时的minmax(0, ...)处理。每个页面都可以尝试用两种方式实现一种用line-based定位一种用grid-template-areas。这样对比之后你会明显感受到两者在不同场景下的优缺点以后看到需求就能本能判断出该用哪种方案。一个比较反直觉的体会是Grid布好之后子元素里的float、margin-left这些老式布局写法通常可以删掉。如果样式表里还有大量position: absolute用来微调位置先看看Grid能否替代。真实项目中我见到过太多用于对齐的绝对定位代码它们本质上只是在使用不合适的布局模型。Grid不是银弹但它确实能让70%以上的传统hack变得多余。你越早把这个模型建立起来后面的布局效率就越高。