ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Flex与Grid布局实战:一维与二维的选型与避坑指南

2026/9/7 22:54:07 拓冰建站 浏览量
Flex与Grid布局实战:一维与二维的选型与避坑指南 CSS布局这块说实话写的人很多但大部分教程都在讲属性怎么用很少有人讲到为什么这么用。我在项目里摸爬滚打了这么久从 table 布局一路用到 Grid中间踩过的坑不计其数。今天就把 Flex 和 Grid 这两套最核心的布局方案掰开揉碎讲清楚既有基础概念也有实际项目里的取舍经验和避坑指南希望能帮你建立一套完整的布局决策思路。1. 先从盒模型说起理解布局的底层逻辑1.1 为什么说盒模型是所有布局的地基很多人在学习 Flex 和 Grid 的时候容易陷入一个误区一上来就背属性justify-content、align-items、grid-template-columns背得滚瓜烂熟但一到真实项目就发现自己还是不会布局。问题的根源在于你对盒模型的理解不够深入没有建立起布局的底层认知框架。CSS 里的所有元素本质都是一个矩形盒子。这个盒子从内到外分别是内容区content、内边距padding、边框border和外边距margin。你们可以把每个元素想象成快递包裹内容是最里面的商品padding 是里面的泡沫填充物border 是纸箱的厚度margin 是包裹和包裹之间的距离。明白了这个底层模型你再看任何布局方案都会有新的理解。因为无论是传统的文档流、浮动还是现在主流的 Flex 和 Grid本质上都是在解决一个问题这些盒子应该放在哪里、怎么排列、怎么响应不同的屏幕尺寸。1.2 传统布局方案的局限性在 Flex 和 Grid 出现之前前端开发者们用的是 table 布局、浮动布局和定位布局。这些方案都有各自的局限性我在项目里全都用了个遍可以负责任地说一句它们处理简单页面还行一旦遇到复杂的响应式需求写起来就是灾难。table 布局的问题在于它把内容强行绑定到了表格结构里页面渲染效率低而且改动一个单元格的位置往往会引发连锁反应。浮动布局的本意是实现图文环绕效果但后来被开发者拿来做了整个页面的布局工具于是各种 clearfix 清浮动的 hack 就冒出来了那段时间写过不少clear: both每次都想砸键盘。定位布局又太死板绝对定位的元素直接脱离文档流后续元素的位置没法自动调整。所以 Flex 和 Grid 的出现不是偶然而是 CSS 布局发展到一定阶段的产物。Flex 专门解决一维布局问题一行或者一列Grid 专攻二维布局行和列同时控制两者各有侧重配合使用能覆盖绝大多数布局场景。我在实际工作中遇到主次不分的块状元素排列用 Flex 居多真要搭建整页骨架就上 Grid各管一摊互不干扰。1.3 Flex 和 Grid 的本质区别很多初学者搞不清楚这两者的区别我打个比方你们就明白了。Flex 布局就像一排排的士兵列队你告诉带队班长从左到右排好、间距均匀、站不下就压缩班长就会自动安排每一个士兵的位置和体型。Grid 布局则更像是一个棋盘你先确定棋盘有几行几列再把每一个棋子放进对应的格子里。关键区别在于Flex 是内容驱动content-drivenGrid 是容器驱动container-driven。这句话怎么理解Flex 布局中即使你不设置任何属性子元素也会根据自身的内容尺寸依次排列你设置的是排列的规则比如方向、对齐、换行、伸缩比例。而 Grid 布局中你必须先定义好网格的结构比如左边一列 200px右边一列占剩余空间然后再把子元素放进去。如果没有显式放入某个网格Grid 的自动放置算法会帮助你自动填充但整体的骨架是容器先定好的。理解了这一点你在项目里做选型时心里就有谱了。排列一堆卡片、按钮、导航菜单等同类元素用 Flex搭建页面的整体骨架、实现复杂的二维排版用 Grid。实践出真知这个选型标准在我几个实际项目里反复验证过。2. Flex 布局深度拆解一维排列的核心机制2.1 主轴与交叉轴Flex 布局的灵魂Flex 布局有一个核心概念叫作主轴和交叉轴。这两个概念如果不搞清楚后面所有的属性设置都会变成试出来的而不是算出来的。主轴由flex-direction属性决定默认是row也就是水平方向。交叉轴与主轴垂直row时交叉轴是垂直方向column时交叉轴是水平方向。justify-content控制的是主轴方向的对齐方式align-items控制的是交叉轴方向的对齐方式。我最早学 Flex 的时候经常搞混后来用了一个土办法记忆justify 管方向flex-direction 的方向align 管交叉垂直方向。设置项目在主轴上是靠左、居中还是靠右用justify-content设置它们在交叉轴上是拉伸、顶部对齐还是居中对齐用align-items。2.2 flex 属性族的真实计算逻辑flex是flex-grow、flex-shrink和flex-basis三个属性的缩写。很多教程只告诉你flex: 1是平均分配但从来不解释背后的计算逻辑这导致很多人在实际项目中一碰到不按预期伸缩的情况就蒙了。我详细拆解一下flex-basis定义了项目在分配多余空间之前的主轴尺寸。它可以是长度值如 200px也可以是百分比还可以是content关键字。默认值是auto此时项目用自身内容的尺寸作为基准。flex-grow定义了当容器有剩余空间时项目如何放大。默认值是 0意思是即使有剩余空间项目也不放大。flex-shrink定义了当容器空间不足时项目如何缩小。默认值是 1意思是空间不够时项目要等比例缩小。举个例子一个容器宽度是 900px里面有三个项目flex-basis都为 200px那么剩余空间就是 900 - 600 300px。如果三个项目的flex-grow都是 1那么每个项目分到 100px最终宽度都是 300px。如果第一个项目flex-grow是 2另外两个是 1那么剩余空间被分为 4 份第一个项目拿 2 份150px另外两个各拿 1 份75px最终宽度分别是 350px、275px、275px。flex-shrink的计算逻辑类似只不过是在空间不足时按比例缩小。我见过不少人在项目里图省事随手写flex: 1。这个写法的完整含义是flex: 1 1 0%意思是 flex-basis 为 0%所有项目从 0 开始等比例放大。如果你的项目里有不同内容的元素这个写法往往会导致所有项目宽度一样内容多被压缩、内容少的留白很大并不是理想效果。更合理的习惯是设置合适的flex-basis让项目先按内容占位再通过flex-grow分配剩余空间。2.3 几个高频踩坑点min-width 与 margin: autoFlex 布局有几个经典的坑我在项目里都亲身踩过第一个就是子元素内容溢出。场景是这样的你给一个 flex 容器设置了两个子元素左边是一个固定宽度的图片右边是一段很长的文字。你本以为右边的文字会自动换行但实际情况是整个容器被撑破了图片被挤得变形。原因在于flex 项目的min-width默认值是auto意思是项目的最小宽度不能小于内容的最小宽度。对于包含长文本或长单词的项目内容的最小宽度可能非常大。解决办法是给子元素设置min-width: 0让项目可以缩小到比内容更小从而触发内部的换行。这个知识点在面试里也是一个高频考点但在实际项目里你会遇到更多类似的隐蔽问题。第二个常用技巧是margin: auto。在 Flex 容器中给某个子元素设置margin-left: auto它会自动吞掉主轴方向的所有剩余空间把这个元素推向最右侧。这个技巧在实现左侧 Logo、右侧操作按钮的导航栏布局时非常好用并不需要为中间元素单独设置间距。2.4 Flex 实战导航栏和卡片列表说了这么多理论我用一个典型的实战场景来演示。假设你要实现一个顶部导航栏左侧是 Logo中间是菜单项右侧是登录按钮为了适配移动端菜单项还要能自动换行。HTML 结构大概是nav classnavbar div classlogoMySite/div ul classmenu li首页/li li产品/li li关于我们/li li联系方式/li /ul div classactions button登录/button button classprimary注册/button /div /nav核心 CSS 这么写.navbar { display: flex; align-items: center; padding: 16px 24px; flex-wrap: wrap; gap: 16px; } .menu { display: flex; gap: 20px; list-style: none; margin: 0; } .actions { margin-left: auto; display: flex; gap: 12px; }注意到没有我用margin-left: auto把右侧操作区推到了最右边用flex-wrap: wrap处理移动端换行再用gap属性统一控制间距省去了一堆 margin hack。这里gap是 Flex 布局后来补齐的能力早期还需要通过 margin 来模拟间距现在主流浏览器都支持了建议直接使用。再来看一个卡片列表的场景要求每行四列超过四列自动换行小屏设备每行显示两列.card-list { display: flex; flex-wrap: wrap; gap: 16px; } .card-item { flex: 1 1 calc(25% - 16px); /* 注意减去的值是 (3 * gap) / 4即 (3*16px)/4 */ min-width: 200px; box-sizing: border-box; }这里的关键是flex: 1 1 calc(25% - 16px)calc是为了在四列基础上减去gap占用的空间。加一个min-width: 200px是为了防止在超窄屏幕上每列太窄导致内容无法阅读同时它也能让flex-wrap提前换行实现响应式的效果。3. Grid 布局深度拆解二维排版的掌控感3.1 网格线与单元格Grid 的骨架Grid 布局的核心概念是网格线grid line和单元格grid cell。网格线是构成网格的横线和竖线单元格是两条相邻横线和两条相邻竖线围起来的区域。网格线的编号从 1 开始从左到右、从上到下递增。所以你定义一个三列的网格就有 4 条竖的网格线编号分别是 1、2、3、4。理解网格线的编号机制对后面使用grid-column和grid-row进行定位至关重要。当我第一次看到grid-column: 1 / 3这种写法时也觉得有点绕后来想明白了1 / 3指的是从第 1 条网格线开始到第 3 条网格线结束跨越了前两个轨道列。如果写grid-column: 1 / -1意思是从第 1 条线到最后一条线占据整行的宽度非常方便。3.2 fr 单位与 repeat、minmax 的配合Grid 布局中有一个独有的单位fr代表剩余空间的一等份。它和 Flex 里的flex-grow有相似之处但使用场景完全不同。简单理解1fr就是一份。grid-template-columns: 1fr 2fr 1fr定义了三列中间列的宽度是左右两列的两倍。fr单位的最强大之处在于可以和其他单位混用。比如你写grid-template-columns: 200px 1fr 2fr;很直观第一列固定 200px剩下的空间被分成 3 份中间列占 1 份最后一列占 2 份。再配合repeat()函数和minmax()函数就能写出非常灵活且优雅的响应式网格。grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));这行代码值得好好琢磨它的意思是创建尽可能多的列每一列的最小宽度是 250px最大宽度是等分剩余空间的1fr。auto-fill是一个很有意思的关键字它会根据容器的宽度自动计算能放下几列。容器宽度是 1000px 时能放 4 列变成 600px 时就自动降为 2 列完全不需要写媒体查询。我当年看到这个属性的第一反应是这也太爽了吧实际用在项目里之后告别了一堆手写媒体查询的代码。不过要注意auto-fill和auto-fit的区别auto-fill在列填不满时会保留空的轨道而auto-fit会把空轨道折叠掉。视觉效果上auto-fit会让空轨道塌缩为 0已放置的项目可以伸展填满容器。实测下来如果希望卡片在容器够宽时能自动拉伸选auto-fit效果更好希望保持网格线绝对对齐就用auto-fill。3.3 grid-template-areas最直观的布局声明如果你是第一次接触 Grid我建议你先从grid-template-areas上手因为它的语义化程度最高代码可读性非常棒。考虑一个典型的后台管理布局顶部是头部导航左侧是侧边栏中间是主体内容右侧是信息栏底部是页脚。用grid-template-areas声明如下.admin-layout { display: grid; grid-template-columns: 220px 1fr 240px; grid-template-rows: 64px auto 48px; grid-template-areas: header header header sidebar main aside footer footer footer; min-height: 100vh; } .header { grid-area: header; } .sidebar { grid-area: sidebar; } .main { grid-area: main; } .aside { grid-area: aside; } .footer { grid-area: footer; }grid-template-areas的可读性太强了任何人看到这段代码都能一眼看懂整个页面的整体布局结构。修改布局时只需要调整 ASCII 艺术图的排列即可比单纯操作网格线容易得多。这里要注意的是grid-template-areas中的每一个矩形区域必须是规整的矩形不能有 L 形或不规则形状而且同一行或同一列中两个同名的区域必须连在一起否则无法构成一个完整的矩形网格区域浏览器会直接判定属性值无效。3.4 Grid 实战经典三栏布局和复杂卡片用 Grid 来实现经典的三栏布局真是降维打击。左右两栏固定宽度中间自适应而且中间内容优先渲染.layout { display: grid; grid-template-columns: 240px minmax(0, 1fr) 240px; gap: 24px; } media (max-width: 768px) { .layout { grid-template-columns: 1fr; } }注意这里又出现了一次minmax(0, 1fr)这个写法很关键。如果你直接写1fr在某些情况下中间栏会被内部的长内容撑破导致页面横向溢出。minmax(0, 1fr)明确指定了中间列的最小值是 0允许它缩小到比内容更小从而触发内部换行效果和 Flex 中设置min-width: 0是一个道理。我在实际项目里试过用display: grid配合grid-template-columns来做报表类的自适应布局。表格数据多、列数多直接设置每个col为1fr浏览器自动等分省去了以前用 JS 动态计算的麻烦渲染性能也非常稳定。再来一个复杂卡片场景一个商品卡片顶部占满全宽左下方是图片右下方是标题和价格整个卡片区域分割得比较自由。用 Grid 的话.product-card { display: grid; grid-template-columns: 120px 1fr; grid-template-areas: banner banner image content; } .product-banner { grid-area: banner; } .product-image { grid-area: image; } .product-content { grid-area: content; }这种非对称的二维布局用 Grid 表达非常自然如果用 Flex 就得嵌套好几层子容器维护成本高出一大截。4. 项目实战Flex 与 Grid 的组合应用与决策4.1 选型标准什么时候用 Flex什么时候用 Grid在我的实际项目经验里选型的核心判断标准就一句话看你要处理的是一维线性排列还是二维平面分布。如果是导航栏、按钮组、标签列表、评论区上下结构这类一行排列或者一列排列的场景毫不犹豫用 Flex。因为它在一维方向上的分布、对齐、间距控制能力极其出色代码量最小。如果是整个页面的骨架搭建、多行多列的卡片墙、需要指定某个元素跨行或跨列的场景就用 Grid。因为它是天然的二维系统你在 Grid 中直接可以描述这个元素跨两列、那个元素在右下角切换到 Flex 里就得嵌套多个容器或者用百分比宽度凑。有一种比较特殊的情况是 Grid item 内部的布局。比如一个 Grid 卡片我想让里面的标题和按钮上下对齐这种情况一般不继续要求 Grid 来管直接给这个卡片加一个display: flex; flex-direction: column;内部就交给 Flex 来处理了。这种混合使用的方式非常常见也是最高效的方案。4.2 响应式策略Grid 负责骨架Flex 负责内容我在项目里有一套自己的响应式策略组合拳整体用 Grid 搭建页面骨架局部用 Flex 处理内容排列最后用 Grid 的repeat(auto-fit, minmax())代替大量媒体查询特殊情况再配合媒体查询做断点调整。比如一个典型的列表页大屏是三栏卡片中屏是两栏小屏是一栏。如果完全用媒体查询写需要写三份样式但用repeat(auto-fit, minmax(280px, 1fr))一行代码就能搞定大部分需求。再配合容器查询container query可以让一个组件根据它的父容器宽度自动调整布局而不是一直监听视口宽度。在组件化开发中容器查询比媒体查询更符合实际场景毕竟是组件自适应容器而不是页面自适应屏幕。4.3 一个真实案例某 SaaS 控制台的响应式改版去年我做了一个 SaaS 控制台的响应式改版算是一个很典型的 Flex Grid 组合案。改版前的页面是用浮动加百分比宽度写的代码里到处是float: left和width: 25%翻看那些代码的时候我能感受到当年写这些代码的前端同事有多痛苦。改版过程中我只保留了一个外层 Grid 布局.dashboard { display: grid; grid-template-columns: repeat(12, 1fr); gap: 24px; } .sidebar { grid-column: span 3; } .main-content { grid-column: span 9; } media (max-width: 1024px) { .sidebar { grid-column: span 12; } .main-content { grid-column: span 12; } }这里用了一个 12 列的网格系统比传统的col-md-4那种框架式网格灵活得多。侧边栏占 3 列主要内容占 9 列加起来正好是 12 列。中间的任何一行卡片、图表、列表全部用 Flex 处理保证元素内部的对齐和间距。整个改版过程中删除的 CSS 有几百行也没有新增任何依赖库布局代码量反而更小了。而且界面的响应式表现和渲染性能都有明显提升同事review的时候直呼改得太清爽了。5. 常见问题与排查技巧实录5.1 Flex 项目不换行内容溢出这是 Flex 布局中出现频率最高的问题。原因通常是某个 flex 子项的最小尺寸是由内容决定的超出了容器的可用空间。排查思路按 F12 打开 DevTools在 Elements 面板中查看该 flex 子项的 Computed 样式注意观察min-width的值。如果它是auto且子项的内容是一长串字符或一张大图片100% 就是内容溢出。直接给该子项加min-width: 0问题基本能迎刃而解。如果是图片元素还可以加max-width: 100%来限制图片不超出父容器宽度。还有一种场景是 flex 容器本身宽度就不够比如容器在某个媒体查询下变窄了但你没给子项设置flex-shrink导致它们撑开容器。这时需要检查容器的flex-wrap是否设置为nowrap如果是考虑改成wrap或者允许项目收缩。5.2 Grid 项目超出预期宽度撑破容器Grid 中有一个隐藏的坑就是grid-template-columns: 1fr 1fr 1fr在某些时候仍然会被长内容撑破因为1fr的底线是min-content当一个格子里有很长的 URL 或者连续的英文字母时这个格子可能比我们预期要宽得多。这种情况下建议改写成grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);如果你写的是repeat(3, 1fr)则可以写成grid-template-columns: repeat(3, minmax(0, 1fr));注意我这里特意强调了minmax(0, 1fr)这是我在大量项目中踩坑踩出来的经验。很多 UI 框架的栅格系统就是靠这招才保证布局不被撑破的。5.3 Flex 和 Grid 嵌套时的高度塌陷问题还有一种搞人心态的情况是外层是 Grid 容器内层是一个 Flex 容器Flex 容器的高度没有被正确计算底部的按钮被裁掉或者溢出。这种问题通常和align-items有关。Grid 容器默认的align-items是stretch也就是让所有网格项拉伸到行的高度。但如果某个网格项内部使用了height: 100%而它的父级并没有显式设置高度就可能出现计算失败的情况。我处理这类问题的时候一般习惯在 Grid 项目的内部容器上设置min-height: 0同时给 Flex 容器设置height: 100%。如果你希望内容自然撑高而不是强制拉伸就把外层 Grid 的align-items改成start这样可以让内部按照内容自动撑高。5.4 兼容性检查清单Flex 和 Grid 的兼容性在主流浏览器中已经很好了但我还是建议在项目发布前过一遍兼容性检查。常用的工具是Can I use搜索css-grid和flexbox就能看到各浏览器的支持版本。需要注意的旧浏览器主要是 IE它对 Grid 的支持基本可以忽略对 Flex 的支持也不完善比如旧的display: box语法还有flex简写属性的一些 bug。如果你的项目还有很大的 IE 用户占比可能需要考虑 Grid 的降级方案比如用 Flex 或者浮动先写一个基础布局然后再用supports (display: grid)包裹 Grid 增强样式确保旧浏览器至少还有一套可用的布局。5.5 调试 Flex 和 Grid 的实用技巧Chrome DevTools 对 Flex 和 Grid 的调试支持做得非常到位建议多加利用。在 Elements 面板中选中一个设置了display: flex或display: grid的元素布局面板会出现相应的可视化工具。对于 Flex你可以直观地看到主轴的起点和终点对于 Grid可以显示网格线的编号、轨道间隙、网格区域的名称。调试 Grid 的时候我经常用 DevTools 的 Grid Editor直接点击界面上的按钮调整网格属性实时预览效果比来回改代码效率高太多。在实际排查布局问题的时候我还习惯用隔离法先加一个高对比度的边框比如红色虚线边框看看这个元素的实际占位区域再一级级检查它的父级和祖父级的布局上下文排查是谁导致了问题。这个方法听起来土但真的很管用。6. 性能与最佳实践让布局代码更健壮6.1 使用 gap 代替 margin 的技巧在早期版本的 Flex 布局中gap属性并不受支持那时候开发者只能用 margin 加一些 hack 来模拟间距。现在gap已经成为 Flex 和 Grid 的标配属性建议团队统一约定使用它。使用gap的好处很明显间距由容器统一控制不会因为项目的 margin 叠加产生意外的双倍间距也不必为容器的最后一个子项单独写margin-right: 0或者margin-bottom: 0这种补丁样式。在使用gap时有个小细节需要注意Flex的gap只支持固定长度值如 16px、1em不像 Grid 的gap那样可以使用百分比值或者fr单位。如果间距需要随容器大小变化还是要老老实实用 margin或者给容器设置内边距。6.2 合理使用 auto-fit 和 auto-fillauto-fit和auto-fill虽然只有一字之差但实际效果有明显区别我在做响应式卡片布局时专门对比过。auto-fill的意思是尽量多地创建轨道即使轨道里没有内容轨道本身依然存在所以容器如果不够宽末尾会出现一段空白区域。auto-fit的意思是尽量多地创建轨道但空轨道会被折叠容器会显示得更饱满项目可以拉伸填满整个容器宽度。在做响应式卡片墙时我一般选择auto-fit因为卡片通常会随容器宽度的增加而变宽视觉效果更好。如果你需要保持固定的对齐网格不管内容多少都不想让空轨道消失选auto-fill更合适。6.3 布局代码的组织与可维护性最后分享一点项目落地的经验布局代码需要单独组织和维护不要和业务样式混在一起写。我目前比较习惯的做法是把布局相关的 CSS 拆成一个 layout 模块用 Grid 定义页面骨架用 Flex 定义组件内部排列再统一用几个 CSS 变量比如--gap-sm、--gap-md、--gap-lg管理间距保持度量单位的一致性。同时尽量少用魔法数字。flex: 1 1 400px中的400px是一个典型的魔法数字如果不加注释后面维护的同事很难理解为什么是 400 而不是 500。我在实际项目中会用命名类名来表达意图比如.card-item--wide这样不看数值也能知道这个卡片比其他卡片要宽。布局代码是 Web 页面风格的骨架骨架稳定了后面的皮肤、组件、动效才有的放矢。建议养成写注释的习惯尤其是 Grid 的grid-template-areas就算你觉得 ASCII 图已经足够表达意图加一行说明文字也能帮后来人省很多时间。我用 Flex 和 Grid 这几年下来最大的感受是布局方案的选型没有银弹Flex 和 Grid 各有所长组合使用才是常态。多在实际项目里练手多拆解优秀网站的页面结构你对这两套布局的掌控感会越来越强。