
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前面那一列是容器一半宽后面那一列再吃掉剩余空间。如果同时再设置gap50%加间距加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系列管的是所有网格轨道作为一个整体在容器里剩余空间的位置。举个例子容器高度800pxGrid内容总共只有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把它重写一遍。布局代码量减少一半、响应式好调很多、验收时设计师挑间距问题的概率也低不少——这个收益只有真正动手重写一次才能感受到。