
1. Grid 为什么能被称为二维布局神器1.1 它到底解决了什么痛点如果你写过几年网页应该还记得那些年我们用 float 拼栅格、用 inline-block 处理间距、用绝对定位手动计算偏移量的日子。一个常见的两栏布局往往要写一整套清除浮动的 hack而遇到左侧固定、右侧自适应这种需求时代码量直接翻倍还要小心嵌套层级过深造成的样式污染。CSS Grid 网格布局的出现从根上把这件事变了。它是浏览器原生支持的二维布局系统也就是说行和列可以同时控制。这个东西的思维方式非常像我们日常用的 Excel 表格或者日历——先画好格子再把内容放进去。你不需要再关心浮动塌陷、不用再为了几个像素的对齐问题反复调 margin因为布局结构本身已经由网格线固定住了内容只会乖乖待在对应的单元格里。我在实际项目里最深的一个感受是用 Grid 之前在 CSS 里写页面骨架跟打仗一样每个区域都要找定位依据用 Grid 之后直接在容器上声明这个页面有三列、两行、间距多少几行代码就把骨架立住了。这种思维跳跃只有真正上手过才会明白。1.2 Grid 和 Flexbox 到底怎么选很多人一听到 Grid 就纠结那 Flexbox 是不是已经废了我给出的答案是没有任何一个工具是多余的关键看你要解决几维的问题。Flexbox 的设计目标是一维布局也就是在一条轴线上处理项目的排列、对齐和空间分配。比如导航栏里的菜单项水平排开、列表项垂直堆叠、按钮组中的间距分配这些用 Flexbox 顺手得不得了。因为你要处理的就是一条线上的事。而 Grid 的目标是二维布局也就是说行和列同时存在、同时可控页面骨架的搭建更适合交给它。这里我用一个表格把选择标准说清楚判断依据推荐方案原因内容是单行或单列排布Flexbox只要处理一个方向代码更简洁需要同时控制行和列Grid二维网格天生支持某一列需要自适应其余固定Flexbox 或 Grid 均可两者都能实现Grid 更适合整体骨架子项需要跨行跨列排版Gridgrid-row / grid-column 是原生能力页面整体骨架 局部组件两者混用外层 Grid 管骨架内层 Flexbox 管组件细节从实践中来谈一个稳妥的组合思路是把页面拆成几个大的区域用 Grid 完成顶部导航、侧边栏、主体内容、底部栏的骨架搭建每个区域内部再根据内容特点用 Flexbox 去处理各种细节比如导航项之间的距离、按钮组中的图标和文字对齐。这种Grid 管骨架Flexbox 管组件的搭配方式是我目前在做项目时最喜欢用的模式也是社区中最主流的实践。2. 网格布局的核心概念一次搞懂2.1 容器、网格线与单元格在写任何 Grid 代码之前有几个关键概念值得花时间理清楚。如果你理解这些基础概念后面学任何进阶技巧都会非常顺畅。首先是网格容器grid container也就是设置了display: grid的元素。它里面的所有直接子元素都会自动变成网格项目grid item。这里有个容易忽略的点只有直接被容器包含的子元素才会参与网格布局嵌套在更深层级里的元素不会被动到。其次是网格线grid line这是我来回强调的一个核心概念。网格线是构成网格结构的骨架它不是实际显示的线条而是划分网格的边界。假设你定义了三列那么纵向就有四条网格线编号从 1 开始从左边数起顺时针依次递增。同样的三行会产生四条横向网格线。网格线可以让你用从第几条线到第几条线的方式精确控制某个项目的位置。最后是单元格grid cell和网格区域grid area。单元格是最小单元由一条横向网格线和一条纵向网格线围成。网格区域则是一个或多个相邻单元格合并后形成的矩形区域。理解了这几个概念你基本就能在脑中快速勾勒出网格的样子了。2.2 三行代码搭起第一个网格纸上谈兵结束了直接上手看一个最基础的例子。假设页面里有一个容器里面有 9 个小子元素我想把它排成一个三行三列的网格。.grid-container { display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px 100px; gap: 10px; }这里grid-template-columns定义了三列每列宽度 100pxgrid-template-rows定义了三行每行高度 100pxgap控制行列之间的间距是column-gap和row-gap的简写属性。运行这段代码后9 个项目会自动按从左到右、从上到下的顺序填充进这 9 个单元格中间距均匀对齐准确。你不需要写任何浮动清除、不用计算百分比减边距就这三行网格已经稳稳地站住了。2.3 fr 单位和 repeat、minmax 函数固定像素值写起来简单但没法自适应。于是 CSS Grid 里引入了fr这个特殊单位。fr的全称是 fraction意思是剩余空间的分数单位。我觉得它特别好理解的方式是把网格轨道想象成一块披萨1fr 1fr 1fr就代表把整块披萨平均分成三份2fr 1fr则表示分成三份第一列占两份第二列占一份。fr和百分比有个重要区别fr是在计算出固定轨道和 gap 之后再对剩余空间进行分配。举个例子一个 1200px 宽的容器设置grid-template-columns: 200px 1fr 2fr; gap: 20px;那么先扣除固定的 200px 和两个 20px 间距剩余 960px然后把这 960px 分成三份中间列占 320px最右列占 640px。这种计算方式在遇到 gap 时不会像百分比那样容易撑破容器更符合直觉。repeat()函数是用来减少重复代码的比如上面的grid-template-columns: 100px 100px 100px可以写成repeat(3, 100px)。它支持的第一个参数也可以是auto-fill或auto-fit这个我在后面响应式布局部分会详细展开。minmax()函数则是设置轨道最小值和最大值的利器比如grid-template-columns: minmax(200px, 1fr)表示这一列的最小宽度 200px最大可以占满剩余空间。这个函数在做响应式时非常常见算是 Grid 里的高频工具。3. 从零到一完整 Grid 布局的实操过程3.1 经典双栏骨架是怎么搭出来的我们先从一个最常见的页面骨架入手左侧固定宽度侧边栏右侧自适应内容区。这种结构几乎在所有后台管理系统、文档站点里都能见到。.layout { display: grid; grid-template-columns: 240px 1fr; min-height: 100vh; } .sidebar { background: #f5f6fa; } .main { background: #ffffff; }HTML 结构就是.layout下面直接放.sidebar和.main两个兄弟节点没有任何多余的包装层。这段代码的效果是侧边栏固定 240px右侧内容区自动占满剩余宽度整个布局高度撑满视口。这里我提到的min-height: 100vh是一个值得记住的经验。因为默认情况下在没有内容撑高的状态下网格容器的高度只由内容决定。如果把容器的高度设为100vh就能保证页面骨架始终占满整个浏览器可视区域内容即使很少布局也不会出现底部露空的情况。3.2 项目的跨行跨列控制骨架搭好之后经常要处理的另一个需求是让某个项目跨多列、跨多行。Grid 里可以用grid-column和grid-row两个属性来实现它们实际上分别是grid-column-start、grid-column-end和grid-row-start、grid-row-end的简写。举个例子我想让第一个项目占据整个第一行.item-1 { grid-column: 1 / -1; }这个1 / -1在 Grid 里非常常用其中的-1表示最后一条网格线。由于网格线的编号是从 1 开始的-1就是最后一条所以grid-column: 1 / -1的意思就是从最左边跨越到最右边也就是占满整行。如果你想跨固定数量的列可以用span关键字。比如grid-column: span 2;表示这个项目横跨两列。需要注意span是相对于项目自身所占宽度的倍数不是绝对列数但它结合repeat使用特别好使。比如在repeat(12, 1fr)的十二列网格里grid-column: span 6;就代表占据六列也就是一半的宽度。3.3 用 grid-template-areas 实现可视化布局如果说grid-column和grid-row是精确控制每个项目的位置那么grid-template-areas就是更直观的画图式布局方式。你可以在容器上直接写出一个 ASCII 字符画的网格结构一眼就能看出页面的形状。.page { display: grid; grid-template-columns: 240px 1fr; grid-template-rows: 60px 1fr 50px; grid-template-areas: header header sidebar main footer footer; min-height: 100vh; } .header { grid-area: header; } .sidebar { grid-area: sidebar; } .main { grid-area: main; } .footer { grid-area: footer; }这种写法的好处有两个第一页面结构一目了然其他同事接手时不用猜每个区域在哪第二调整布局结构时只需要改grid-template-areas的字符排列不用挨个去改子元素的属性。.符号可以代表空白单元格比如你想让 footer 只在主内容区下方不横跨侧边栏可以写成grid-template-areas: header header sidebar main footer footer;这里 footer 区域就从横跨两列变成了只占右侧列。这种通过字符分布来调整布局的方式在改版时效率非常高我常常为了快速验证多种布局方案就只改动这一块的字符内容比之前逐个属性调试快得多。4. 复杂响应式布局的实用技巧4.1 告别媒体查询auto-fill 与 auto-fit 的魔力传统响应式布局的思路是在不同断点下用媒体查询去覆盖列的宽度值。比如 1200px 宽时三列、768px 宽时两列、手机上一列。这套思路有效但代码重复率高而且断点之间总有一个过度宽度可能让布局看起来很奇怪。Grid 给了我们一种新的解答让网格轨道自己根据容器宽度决定数量。核心写法是这样.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; }这段代码的意思是每一列的最小宽度是 280px最大是 1fr也就是等分容器的剩余空间浏览器会在这个区间内自动决定到底放几列。容器宽的时候多放几列窄的时候少放几列整个过程完全不需要任何媒体查询。我实测下来的体验是在 1200px 宽的容器里这个布局通常能放 4 列在 768px 容器里变成 2 列在 400px 的窄屏上就只剩 1 列了。每一个宽度区间浏览器都能找到最适合当前空间的列数而且项目之间的间距始终保持一致。4.2 auto-fit 与 auto-fill 到底差在哪很多人在这里容易踩坑。auto-fill和auto-fit在容器宽度足够大、能放下多个轨道时表现几乎一样差别只有在轨道数量较少、容器还有剩余空间时才显现出来。auto-fill的翻译是自动填充只要空间允许它就会尽可能地创建空轨道哪怕这些轨道里没有内容。所以如果你只有两个项目容器宽度能容纳 5 个 280px 的列那么auto-fill会创建 5 个轨道其中 3 个是空的剩余空间被这些空轨道占满了实际内容并不会被拉伸。auto-fit的翻译是自动匹配它会先创建轨道然后把没有内容的空轨道折叠掉让有内容的轨道来瓜分剩余空间。所以同样只有两个项目容器宽度能容纳 5 个 280px 的列时auto-fit会让这两个项目各自扩展到更宽平均分配剩余空间。放到实际场景里如果你做的是一个卡片墙希望卡片数量少时也能保持较大宽度、铺满整行那应该选auto-fit如果你希望卡片有固定的最大体验宽度即使后面留下空白也不让卡片过宽那auto-fill更合适。4.3 网格对齐justify 与 align 的完整解答Grid 的对齐分两个维度一个是对齐网格轨道本身另一个是对齐单元格里的内容。这两个方向不能混淆。justify-content和align-content用来控制整个网格轨道在容器中的位置前提是网格总宽度小于容器宽度。比如容器 1200px三列各 200px这时候网格总共只有 600px 宽你可以通过justify-content: center;把网格整体居中。可用的值包括start、end、center、stretch、space-between、space-around、space-evenly跟 Flexbox 里的概念很像。justify-items和align-items则用来控制网格项目在其单元格内部的对齐方式。默认值是stretch也就是项目会拉伸填满整个单元格。如果你把它改成center项目就会以自己的内容宽度居中显示。justify-items控制水平方向align-items控制垂直方向。如果两个属性要设置相同的值可以用place-items简写比如.grid { place-items: center; }这句话让所有网格项目在各自单元格内同时水平和垂直居中。这个写法在做照片墙、卡片居中等场景时非常高效。我还想提醒一个细节align-items的默认值stretch会导致子元素在垂直方向被拉满如果你发现子元素高度异常先考虑这个属性是否处于默认状态。5. 实战案例三种最常用的 Grid 布局模板5.1 案例一顶部导航 侧边栏 内容区后台管理骨架后台管理系统的页面骨架几乎都是顶部导航 左侧菜单 右侧内容的结构。我用 Grid 实现时会这样写.admin-layout { display: grid; grid-template-columns: 220px 1fr; grid-template-rows: 56px 1fr; grid-template-areas: topbar topbar sidebar main; height: 100vh; } .topbar { grid-area: topbar; background: #2d3436; color: #fff; } .sidebar { grid-area: sidebar; background: #f5f6fa; overflow-y: auto; } .main { grid-area: main; padding: 24px; overflow-y: auto; }这是我做过好几个后台系统后总结出的通用模板。顶部导航横跨两列侧边栏和主内容区在下方各占一列。overflow-y: auto单独加在侧边栏和主内容区上这样两边的滚动可以独立控制不会互相影响。这个模板的可扩展性也很高后续要加底部状态栏只要在grid-template-areas里加一行footer footer再给它分配一个grid-area就行。5.2 案例二自适应卡片墙商品展示页、图片画廊、博客文章列表这类卡片式布局是最常见也最适合 Grid 发挥的场景。代码其实在上面 4.1 节已经出现过但把它放在案例场景里会更直观.card-wall { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; } .card { background: #fff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); padding: 16px; }实际项目中我会把卡片的基础样式放在.card里再通过minmax(280px, 1fr)让浏览器自动计算列数。这种布局在电商页面中表现特别好因为每张卡片的内容高度不固定Grid 会按照行的最高项目来统一该行高度卡片之间天然对齐不需要额外写flex-grow之类的 hack。5.3 案例三十二列栅格系统很多前端框架里都有十二列栅格系统。如果你想不依赖任何框架、自己实现一个灵活的十二列网格Grid 完全可以做到.grid-12 { display: grid; grid-template-columns: repeat(12, 1fr); gap: 16px; } .col-3 { grid-column: span 3; } .col-4 { grid-column: span 4; } .col-6 { grid-column: span 6; } .col-12 { grid-column: span 12; }这里repeat(12, 1fr)把一行平均分成十二等份任何子元素只要指定grid-column: span n就能占据 n 列宽。比如col-6占据 6 列正好一半宽度col-4占据三分之一宽三个col-4放一起就是完整的整行。这套组合非常灵活响应式时只需要在不同断点覆盖类名即可。我做过的一次改版就是把原来用 Bootstrap 栅格的页面给换成了自己写的这套 Grid 栅格类。好处是体积小、没有额外依赖而且gap来控制间距比原来的padding方案干净很多不会出现边缘多出半个间距的问题。6. 常见问题与避坑心得6.1 问题速查表排错效率瞬间翻倍我在带团队和帮同事排查 Grid 问题的时候发现很多坑是反复出现的。所以我整理了一个速查表把这些常见问题的现象和原因都列出来方便你以后直接对照问题现象最常见原因解决办法项目不按预期位置排列没有给容器设置grid-template-columns隐式网格按行自动排列显式定义列宽或调整grid-auto-flow设置了gap但间距没生效写的是gap: 10而不是gap: 10px单位缺失被忽略统一加单位Grid 不像line-height可以省略单位子元素被拉伸得过高align-items默认值是stretch项目会填满网格区域高度按需改为start/center内容被压缩成一团网格轨道设置了固定小宽度而内容无法换行使用minmax()给最小宽度留余量容器溢出横向滚动条项目内容太长或百分比列宽加gap导致总宽度超出改用fr或minmax(0, 1fr)分配剩余空间grid-row: 1 / 3了但没跨行可能被其他隐式规则覆盖检查是否同时写了grid-area用开发者工具查看实际计算样式这里面我特别想强调一个容易忽略的细节minmax和1fr搭配时的行为。比如grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))如果每一列的最小宽度是 200px而你的卡片内容里有很长的英文单词或很长的链接默认情况下单词不换行内容就会把轨道撑破导致页面出现横向滚动条。解决方案是把minmax的最小值写成0也就是minmax(0, 1fr)。这样网格轨道可以被压缩到 0 宽度内容会根据自身宽度换行而不是撑破轨道。这个技巧在处理大量文本内容时特别实用建议直接背下来。6.2 性能、兼容性与调试建议CSS Grid 在性能上表现相当出色因为它在浏览器底层是用专门算法计算布局的比用 JavaScript 手写一套栅格系统要高效得多。即便页面上有成百上千个网格项目渲染性能也不会受影响太多。真正影响性能的往往不是 Grid 本身而是你在网格项里塞了大量重排、重绘的动画效果。兼容性方面从 2020 年之后发布的主流浏览器版本Chrome、Firefox、Safari、Edge都对 Grid 有了完整支持。如果你的用户还在使用 2018 年之前的旧浏览器那就需要注意一下不过目前绝大多数实际项目的浏览器环境都已经支持了。稳妥的做法是对于旧版本浏览器页面至少要保证内容按顺序正常显示即便没有漂亮的布局也不影响阅读和使用。调试 Grid 时我墙裂建议打开浏览器的开发者工具。在 Elements 面板选中一个设置了display: grid的容器浏览器会直接高亮显示所有网格线和网格轨道编号你甚至可以在工具里调整轨道的尺寸在线预览效果。这个可视化调试能力极大降低了排错成本强烈建议所有新手都试用一下。最后再分享一个我在实际问题排查中的心得当你觉得 Grid 布局表现怪异的时候第一反应不要去想是不是样式被覆盖了而是先在开发者工具里查看这个元素继承的网格线、网格区域到底是什么。很多时候问题出在行号或列号的计算和预期不符而不是代码逻辑本身错了。把网格线和区域可视化打开往往一眼就能找到原因。我刚开始学 Grid 的时候靠的是把上面这些案例全部手打一遍然后逐个属性去改、去观察变化。这个过程中踩了不少坑但也正是因为踩过这些坑才真正理解了网格线和 fr 单位背后的逻辑。跟着把这篇文章里的示例都写一遍你也会发现页面布局这件事确实可以变得很轻松。