☰
CSS Grid布局完全指南:从基础原理到实战模板与踩坑排查
2026/10/9 16:05:11 网站建设 项目流程

1. 我为什么把页面布局全部换成了Grid

写CSS这么多年,我最怕的不是某个属性记不住,而是明明写完了布局,一刷新页面就歪得没法看。早些年用float做三栏布局,清浮动要用overflow:hidden、伪元素、clearfix三条路子轮着试;后来Flexbox出来以后好了一些,但只要涉及到二维结构——也就是同时要管横和竖两排元素——Flexbox就有点不够用了,需要用嵌套flex、固定高度、百分比宽度各种腾挪,代码越写越绕。

后来我在实际项目里全面切到CSS Grid,才真正体会到什么叫“为布局而生的属性”。Grid最核心的价值在于,它是二维布局系统,它可以同时控制行和列;而Flexbox本质是一维布局,要么排一行,要么排一列。两者不是替代关系,而是分工关系。我这篇内容就是想把我实际工作中最常用的Grid布局套路整理出来,从原理到写法再到踩坑,一次性说透。适合正在学Grid的初学者,也适合用了很久但想补一补细节的中级开发者来对一下自己的理解。

目前我手头维护的几个后台系统、内容站、活动页,骨架全部是Grid搭的。如果你最近也在纠结页面怎么布局更高效、代码怎么更少、响应式怎么更好调,那这套内容可以给你一个直接的参考方案。

2. 先搞懂Grid的坐标系,后面所有布局都是“填空”

学Grid容易卡壳,主要不是因为属性多,而是因为它的思维方式和传统的文档流差别比较大。传统布局是“从上往下排”,Grid把页面当成一张网格纸,你把元素放进格子就行。只要把坐标系的心理模型建立起来,后面看什么属性都顺。

2.1 容器、项目、网格线、网格单元之间的关系

一个最基础的Grid结构分两层:外层是Grid容器,内层直接子元素是Grid项目。这句话很关键,因为Grid只对其直接子元素生效,如果你的项目内又套了别的元素,那里面那一层跟外层Grid就完全没关系了,需要另起Grid或者用Flexbox处理。

容器一旦设置了display: grid,浏览器就会把容器内部空间切分成一个个网格。切割之后,横向和纵向会形成网格线,网格线围起来的最小单位叫做网格单元,也就是一个格子。轨道是相邻两条线之间的整行或整列,网格面积则是你可以把多个格子合并成的一块区域。

我一般这样跟同事解释:网格线就像Excel表格里的框线,轨道就是一行或者一列的“高度/宽度”,网格面积就是选中多个单元格后合并出来的大单元格。你往页面填内容,其实就是把一个元素放到一个或者多个合并后的单元格里。

2.2 一条最简单的Grid声明长什么样

看一段最小代码:

.container { display: grid; grid-template-columns: 200px 1fr 200px; grid-template-rows: auto; gap: 16px; }

这行grid-template-columns: 200px 1fr 200px的意思是:容器内部切三列,第一列固定200像素,第二列用1fr把剩余空间吃掉,第三列再固定200像素。1fr可以理解成“分配剩余空间的一份”,两个1fr就是平均分剩余空间,三个1fr就各拿三分之一。这个单位是Grid里使用频率最高的,后面专门讲。

再看grid-template-rows: auto,意思是行高随内容自动撑开。大多数情况下你不需要精确指定行高,让内容自己决定高度更安全。

这里的gap: 16px是Grid的间距属性,直接给轨道之间加缝隙,不用再去项目上写margin了。这个属性同时作用于行间距和列间距,如果你想让行间距和列间距不同,写成gap: 16px 24px,前面是行,后面是列。注意,早年写Grid的老代码里可能会出现grid-gap,现在已经统一用gap了,后者同时兼容Flexbox,写gap就对了。

2.3 从“心理模型”到“一行代码”的转换

很多人学Grid喜欢背属性,其实没必要。你只需要记住:Grid布局本质是定义轨道、放置项目、调整间隙。定义轨道用grid-template-columns和grid-template-rows;放置项目用grid-column和grid-row,允许项目跨列跨行;调整间隙用gap;对齐用justify-*和align-*系列。剩下的都是这四件事的参数变体。

这里多说一句我的体会:不要在没建立模型之前就去记grid-template-areas那套写法,因为命名的前提是你已经能理解网格线怎么划分。先用手动指定起点终点的方式练手,比如grid-column: 1 / 3,意思是这个项目从第1条纵向网格线开始,到第3条纵向网格线结束,横跨两列。一旦这个动作熟练了,区域命名就是它的语法糖而已。

3. 直接可以抄的六种常用Grid布局模板

这节是我最想分享的内容。我在真实项目里反复用到的Grid布局就那几种,每种都有固定的套路。你把这些模板抄走,就能覆盖绝大多数页面的骨架需求。

3.1 经典三列布局:侧边栏+主内容+侧边栏

这是后台管理系统里最常见的骨架样式。

.layout { display: grid; grid-template-columns: 240px 1fr 240px; grid-template-rows: auto 1fr auto; min-height: 100vh; gap: 20px; }

对应的HTML结构大致是顶部header、中间主体、底部footer,如果你想让整个页面铺满视口高度,min-height: 100vh加上grid-template-rows: auto 1fr auto这一组特别重要:第一行和第三行的高度完全由内容决定,中间那一行吃掉所有剩余高度。这样不管内容多还是少,footer都稳稳地在页面底部,不会出现内容少时footer跑到页面中间的尴尬。

3.2 栅格系统:12列等分布局

如果你经常做活动页或者数据看板,一定会需要类似Bootstrap的栅格布局。用Grid实现极其简单:

.row { display: grid; grid-template-columns: repeat(12, 1fr); gap: 16px; } .col-4 { grid-column: span 4; } .col-6 { grid-column: span 6; } .col-8 { grid-column: span 8; }

repeat(12, 1fr)表示重复12次,每次1份,所以12列均分宽度。grid-column: span 4的意思是这个项目横跨4列。这里用span关键字比写起点终点好在哪里?好处是你不用管具体从哪条网格线开始,浏览器自动帮你找一个合适的位置,代码也更短。12列的栅格其实来自于传统排版的经验:12可以被2、3、4、6整除,组合非常灵活,两个col-4一行能放下三个,三个col-4一行刚好,三个col-6一行能放两个。

3.3 圣杯布局:头部+侧边栏+主内容+侧边栏+底部

圣杯布局是前端面试的常客,也是很多内容站的标准骨架:顶部一条通栏,中间左中右三列,底部一条通栏。用Grid的grid-template-areas做是最优雅的:

.layout { display: grid; grid-template-columns: 240px 1fr 240px; grid-template-rows: auto 1fr auto; grid-template-areas: "header header header" "sidebar main aside" "footer footer footer"; min-height: 100vh; gap: 20px; } .header { grid-area: header; } .sidebar { grid-area: sidebar; } .main { grid-area: main; } .aside { grid-area: aside; } .footer { grid-area: footer; }

区域命名法的好处是,你光看grid-template-areas的字符串,就能在脑子里画出整个页面的结构图。它直接把布局的过程变成了“规定地图区域”,完全不需要去计算网格线的编号。我强烈建议你写这种整体骨架时用区域命名法,后期改动位置只需要交换字符串顺序,HTML结构几乎不用动。

这里有个使用注意:字符串里每一行代表一行轨道,每一行的列数必须一致,否则整个grid-template-areas属性会失效。比如第一行写了三个名字,第二行却写了四个,浏览器直接报错。遇到这种问题,检查一下每一行的名字数量是否相同。

3.4 等分卡片网格,一行自动放N个

后台系统里最多的是卡片列表。以前需要算百分比宽度、考虑间距、处理换行,用Grid就是一句repeat的事:

.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }

这是固定一行放三张卡片,多出来的卡片自动换到下一行,每行三列的高度自动对齐。如果你不知道屏幕宽度下能放几张,想让浏览器自己决定,把写死的3改成auto-fill或者auto-fit配合minmax,这个我放到后面响应式部分专门展开。

3.5 合并单元格式展示区,处理重点内容

有时候你想让某个重要卡片占两列、另一张占一行,类似表格的合并单元格。用Grid实现特别灵活:

.cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; } .card-primary { grid-column: span 2; grid-row: span 2; }

一个元素同时跨两列和两行,它就变成了一个大方块,周围的小卡片自动围绕它排布。这种大卡片+小卡片的组合很适合做数据看板的重点指标展示,视觉层次一下就出来了。需要提醒的是,当出现跨行跨列项目时,浏览器放置项目的顺序可能跟你想的不一样,因为Grid默认是按行逐列填充的,碰到放不下的就会自动换行。这个行为可以通过grid-auto-flow: dense来改变,让浏览器尽量填满空格子,后面踩坑部分再细说。

3.6 两列布局:一固定一自适应

文章页、聊天界面、设置页经常用到左固定右自适应的两列布局:

.two-cols { display: grid; grid-template-columns: 300px 1fr; gap: 24px; }

左列固定300px,右列吃掉剩余全部宽度。如果你想要左侧宽度由内容决定,不希望它被压缩,把300px换成auto也行。另一种常见变体是左侧minmax(240px, 300px),意思是宽度在240px到300px之间浮动,内容多时加宽,内容少时收窄,但不会小于240px。这个写法我在设置页的左侧导航栏里用得多,因为导航项如果变成英文或加长文案,容器能自动扩展,不会溢出。

4. fr、minmax、repeat常用轨道写法详解

模板抄了之后,很多人会在轨道尺寸的细节上翻车。轨道尺寸是Grid布局里最需要动脑的地方,大部分“为什么我的Grid和设计稿不一样”的问题,根源都在这里。

4.1 fr单位到底在分什么

fr的全称是fraction,意思是“份”。它分配的是剩余空间,而不是总空间。这个区别非常关键。举个例子:

grid-template-columns: 1fr 1fr 1fr;

如果容器宽度是900px,也没有gap和固定列,三列各拿300px,看起来像是三等分。但假如项目里有固定宽度的列:

grid-template-columns: 200px 1fr 2fr;

容器900px,先扣掉200px,剩700px,然后这700px被分成3份,第一列拿到约233px,第二列约467px。这里的1fr和2fr是在剩余基础上分配的,不是基于总宽度。这个机制意味着,如果你把一个300px固定列和几个1fr混用,其他列宽会自动收缩补偿,不会出现总宽度超出容器的情况。

还有一点需要记住:fr的最小值是0,它不像百分比那样有最小内容宽度的隐式限制。如果另一个项目的内容很长,1fr对应的轨道也不会因为内容撑破布局,因为项目默认有min-width: auto,这个后面讲overflow的时候再提。

4.2 minmax:给轨道设一个弹性范围

minmax(min, max)用来定义一个轨道的最小和最大尺寸,参数可以混用固定值和fr。最常用的写法是:

grid-template-columns: minmax(200px, 1fr) 2fr;

意思是第一列至少200px,最多吃掉1份剩余空间;第二列固定吃2份。当容器宽度过窄时,第一列收缩到200px就不再缩了,第二列会优先被压缩。这样能保证重要列有一个最低可读宽度。这个组合在响应式布局里几乎是万能的。

minmax还可以配合auto使用,比如minmax(auto, 1fr),auto表示最小值由内容决定,内容多宽列至少多宽,比写死一个数更贴合实际尺寸。我在展示用户昵称、长标题这类不确定长度内容时,经常用这个写法,避免内容被压到换行。

4.3 repeat配合auto-fit与auto-fill实现自适应列数

这是Grid最惊艳的能力:卡片数量不变,屏幕变宽列数自动变多,屏幕变窄列数自动变少,不写一条媒体查询。

.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 20px; }

这句话怎么理解:列宽的“底线”是260px,只要空间足够,浏览器就继续增加列;每个列在满足260px底线后,如果还有剩余空间,就均分吃掉。auto-fill会一直创建虚拟的空轨道,就算没有足够项目填充它们,轨道依然占位;而auto-fit会把空轨道折叠掉,让有内容的卡片撑满整行。两者的区别在项目少于轨道数时特别明显。比如一行能放4列但只有3个卡片,auto-fill右边会留下一个空位,auto-fit则会让3个卡片均分拉伸铺满。

实际使用中,我90%的情况用auto-fit,因为空位在视觉上总是奇怪,卡片铺满整行更整齐。只有在需要严格保持网格形状的场景才用auto-fill。如果你发现列数跟预期不一致,先检查是不是这两个关键字用错了。

4.4 百分比、rem、px混用时的注意点

轨道尺寸支持所有长度单位,混用没问题,但要注意百分比基于的是网格容器的宽度,而不是剩余宽度。所以grid-template-columns: 50% 1fr,前面那一列是容器一半宽,后面那一列再吃掉剩余空间。如果同时再设置gap,50%加间距加1fr可能超过容器宽度,导致溢出。这种情况下优先用fr替代百分比,fr区间本身就是剩余空间计算的,天然不会溢出。

rem和px的区别在桌面端其实不大,都是固定尺寸,但rem跟着根元素的字体大小走。如果你有“整站字号调整”的需求,用rem写轨道宽度可以跟着一起缩放,移动端适配更舒服。我一般把系统的字体大小设为font-size: 16px,于是1rem = 16px,写grid-template-columns: 15rem minmax(0, 1fr)就相当于左列240px。这样跟设计稿换算的时候,脑子里直接除以2就行,很快。

5. 对齐与间距,这是让布局“看起来舒服”的细节

轨道尺寸解决了结构问题,但页面好不好看,往往取决于间距和对齐。Grid里对齐相关属性比较绕,尤其是justify和align同时出现的时候容易晕。

5.1 再谈gap:内间距的神器

gap是Grid里我最喜欢的属性之一,没有它之前控制间距靠margin,还要考虑“第一个元素不要左边距、最后一个不要右边距”这种边界问题。有了gap,间距交给容器管理,项目之间自动有均匀缝隙,不会多出来也不会少。在Flexbox里gap同样可用,所以可以放心使用。

gap还支持row-gap和column-gap分开设置,比如grid-template-columns: 1fr 1fr配column-gap: 32px,行间距为0,适合横向排列时间线、步骤条这种只需要列间距不需要行间距的场景。

5.2 justify-items与align-items:控制项目在格子里的位置

Grid的每个项目都被放进一个网格单元里,如果这个网格单元比项目大,项目在单元里怎么摆,就由这两个属性决定。justify-items控制水平方向,align-items控制垂直方向,默认值是stretch,也就是拉伸填满格子。这就是为什么Grid项目默认会撑满格子,而不是像普通块级元素那样只占一行。

如果你想做一个完全居中的登录框,这可能是最简单的写法:

.parent { display: grid; place-items: center; }

place-items是align-items和justify-items的简写,place-items: center同时把水平和垂直居中都搞定。这是我遇到的“垂直居中”问题最省事的解法,没有之一。

5.3 项目自身对齐:justify-self与align-self

容器上的justify-items管的是所有项目,但某个特定项目想在其他格子里特殊对齐,就用justify-self和align-self覆盖。使用场景很典型:卡片里的图标、徽标、按钮位置需要单独调整时,给这个项目单独设置对齐方式,不用影响兄弟元素。简写同样是place-self。

5.4 内容富余时怎么处理:align-content与justify-content

*-items系列管的是项目在格子内部的位置,*-content系列管的是所有网格轨道作为一个整体,在容器里剩余空间的位置。举个例子:容器高度800px,Grid内容总共只有600px高,剩下200px怎么分配?默认是留在底部或顶部?都不是,默认是从顶部开始排,底部留空。如果你想让它上下居中,可以设align-content: center;想让行与行之间平均拉开,可以设align-content: space-between。这个功能在页面整体内容较少、又需要保持视觉垂直平衡时非常有用,比如空状态页、错误页、结算成功页。

6. 实战:用Grid从零搭建一个内容后台页面

前面几节都是基础,这节我用一个具体的后台页面来串一遍整个设计过程。需求很简单:顶部导航条、左侧菜单栏、中间内容区、右侧快捷面板、底部版权区,中间内容区还需要放一组卡片统计。

6.1 第一步,画出骨架图再写代码

我几乎每次写Grid之前都会先在纸上或者脑子里画一下“格子地图”。这个页面长这样:第一行header占通栏;第二行左中右三列;第三行footer占通栏。中间列还可以再细分卡片区。这张图对应到代码就是:

.dashboard { display: grid; grid-template-columns: 220px 1fr 280px; grid-template-rows: auto 1fr auto; grid-template-areas: "header header header" "sidebar main aside" "footer footer footer"; min-height: 100vh; gap: 16px; }

HTML结构里,header、sidebar、main、aside、footer五个部分直接对应grid-area的名字。这时候页面骨架就搭完了,总共一行CSS,比你用float或flex嵌套几十行强得多。

6.2 第二步,填充中间内容区的卡片网格

主内容区里面的卡片组是独立的Grid嵌套。外层Grid决定了卡片组的整体区域,内层再用Grid排列卡片:

.main-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; }

这里我用auto-fit而不是写死三列,是因为后台宽度在不同屏幕上变化明显,自动列数可以保证卡片在不同宽度下都能完整显示。如果你在设计稿里看到的是固定三列,那就用repeat(3, 1fr),响应式的问题由外层整体布局承担,内层追求精确还原。

6.3 第三步,处理局部一行按钮和输入框的组合

主内容区里经常有搜索栏,左边一个输入框右边一个按钮。这是典型的“一维排列”,用Flexbox比Grid合适:

.search-bar { display: flex; gap: 12px; } .search-bar input { flex: 1; }

让输入框用flex: 1吃掉剩余宽度,按钮固定自然宽度。这个场景没必要启用Grid的二维能力,Grid和Flex混合使用是最合理的,就像骨架用Grid,血肉用Flex。很多初学者容易犯的错是到处都用Grid,反而把代码写复杂了。

6.4 第四步,加响应式细节

当屏幕变窄,三栏可能放不下。我一般这样处理:在768px断点下把右侧面板挪到主内容下方,变成两列;在576px断点下把所有列合并成单列。用媒体查询覆盖grid-template-areas即可:

@media (max-width: 768px) { .dashboard { grid-template-columns: 1fr; grid-template-areas: "header" "main" "footer"; } .sidebar, .aside { display: none; } }

这个过程中你会发现,改Grid响应式布局,真正需要改的就是grid-template-columns和grid-template-areas这两行,HTML一行都不用动。这在我写后台系统的实践里节省了大量时间。

6.5 第五步,验证间距和对齐是否符合设计稿

骨架搭好之后,我会花几分钟检查gap与设计稿的间距是否一致。设计稿里的24px间距对应代码中的gap: 24px;中间区域内部卡片的间距可能是16px,因为它们是另一个Grid上下文。如果你的设计稿同时有24px和16px两种间距,不要试图用同一个容器的gap解决,该嵌套就嵌套,该用padding就用padding。Grid负责结构,具体内边距还是得由项目自己的padding控制。很多人布局看着“差一点”,就是这个层次没理清。

7. 那些让我排查半天的Grid坑

Grid看着简单,实际运行时的行为有一些隐蔽的坑。下面这几条几乎都是我踩过的,写出来帮你少走弯路。

7.1 隐式网格是怎么悄悄出现的

如果你没有写grid-template-rows,而Grid项目数量超出一行能容纳的数量,多出来的项目会自动创建新行,这就是隐式网格。问题在于:显式行如果设置了grid-template-rows: 100px,这个高度只作用于显式行,隐式行的高度是auto,内容多高它就多高。如果你希望隐式行也有固定高度,需要用grid-auto-rows来定义:

.cards { display: grid; grid-template-columns: repeat(3, 1fr); grid-auto-rows: 160px; }

这样超出的行自动获得160px高度。卡片墙、图片墙、日历格子这类需要所有格子等高对齐的场景,grid-auto-rows几乎是必设项。忘了它,网格就可能呈现“有的高有的矮”的错落状态,看起来像Content Layout Shift,其实只是隐式行没定义。

7.2 gap不生效的最大可能原因

我遇到过几次gap不生效的情况,排查半天发现是容器本身不是Grid或Flex。检查一下display: grid是不是写在了正确的选择器上,子项目上写margin也可能跟gap叠加,导致间距看起来不一致。另外旧浏览器对gap在Flexbox的支持不完全,如果你的页面要兼容特别老的浏览器,Flexbox场景还是得退回margin方案。

7.3 grid项目里的内容溢出问题

Grid项目默认min-width: auto,这里的auto是“内容最小宽度”。如果一个项目里有超长英文字符串或者很长的URL,这个项目的轨道会被内容撑宽,导致整列超过设计宽度。解决方案是显式把项目的min-width设成0:

.main { min-width: 0; }

或者用minmax(0, 1fr)来定义轨道,这里的0是说轨道最小值可以压缩到0,内容溢出时由项目内部自己处理(比如overflow-wrap: break-word)。这个问题在聊天消息列表、表格数据、长链接展示场景里几乎必现,我在第一次遇到时排查了将近一个小时。

7.4 dense填充模式下项目顺序变乱的错觉

grid-auto-flow: dense可以让小项目填进大项目留下的空洞里,视觉上密实,但代价是项目顺序可能偏离DOM顺序。这个行为不是bug,是dense设计本来的样子。如果你的页面依赖项目顺序(比如时间线、步骤条),别开dense,否则屏幕阅读器和Tab顺序都会显得混乱;如果是图片瀑布流、卡片堆叠这类不强调顺序的展示,可以放心使用。

7.5 DevTools里的Grid Inspector是排错第一工具

遇到Grid布局不对,我建议第一件事不是猜代码,而是打开浏览器开发者工具,找到Grid选项卡,它会显示出所有网格线、轨道大小、区域名称,一目了然。Chrome和Firefox都原生支持。说实话,这几年排查Grid问题,有一半靠这个工具解决,比如能看到某个轨道为何被撑宽、某个区域是不是没有关联上grid-area的名称(通常表现为灰色的未分配区域)。养成打开工具看网格的习惯,学Grid的速度会快很多。

8. 最后分享几个我从项目里沉淀下来的Grid习惯

收尾处不讲套话,就分享三个我现在写Grid布局时一直在坚持的习惯,这也是我踩过很多坑之后换来的。

第一个习惯是“先整体后局部,先Grid后Flex”。我在任何页面布局开始前,先问自己这个区域的元素是二维结构还是一维结构。整体骨架几乎无脑用Grid,局部一行内的按钮、输入框、标签组合用Flex。这样分工之后,代码结构特别清晰:看display: grid就能知道这是个区块骨架,看display: flex就知道这是单行排列,排查问题时扫一眼选择器就能圈定范围。

第二个习惯是“显式写出grid-auto-flow和grid-auto-rows”。网格项目少的时候,这两个属性不写也无所谓;但项目一多、跨行跨列之后,隐式网格的行为就会参与布局。与其等出问题再查,不如创建Grid时就带上grid-auto-rows: auto或者明确的行高,至少给自己留一个明确的调试起点。同理,项目要跨行跨列的,我也都会显式写grid-column和grid-row,不依赖默认顺序。

第三个习惯是“所有关键区块都用grid-template-areas命名”。虽然手动指定行列的写法更灵活,但grid-template-areas的好处是结构一目了然,后期接手的人看代码字符串就能还原出页面布局。尤其维护老项目时,这段CSS字符串基本就是页面结构的活地图。我甚至会在笔记里为每个常用布局保存一张对应的grid-template-areas快照,新项目直接复制改名字。

就到这里。这套Grid常用布局的打法,覆盖了我日常开发里至少九成的页面骨架需求。如果你是按着顺序读下来的,我建议你现在就找一个项目里的旧页面,用Grid把它重写一遍。布局代码量减少一半、响应式好调很多、验收时设计师挑间距问题的概率也低不少——这个收益,只有真正动手重写一次才能感受到。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询