ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Flex与Grid布局深度对比:一维与二维的选型实战指南

Flex与Grid布局深度对比:一维与二维的选型实战指南 写前端写过半年以上的人几乎都会在某个改样式的深夜面对同一个问题这块布局到底用 Flex 还是 Grid标题里把它们拆成“Flex布局和Grid布局”两个词看起来像是两个并列选项但真实项目里它们既不是竞争关系也不是随便二选一的关系。我最早也背过“一维二维”的标准答案可真到了要做一版后台看板或者一个卡片墙加筛选条的页面时照样会犯迷糊。这篇东西就打算把两者的本质差别、常用属性、选型思路以及我在实际项目里踩过的坑一次性聊透。适合刚学完选择器和盒模型、正打算把布局能力补齐的初学者也适合写了一阵子 CSS 但总拿不准“哪个场景该用哪个”的朋友。1. 一维与二维的本质差异先搞清楚Flex和Grid到底在解决什么事1.1 Flex 的主轴思维所有子项都在一条线上流动Flex 的全称是 Flexible Box翻译过来是“弹性盒子”。它的核心工作方式是先确定一根主轴然后把所有子项沿着这根主轴依次排列。主轴方向由flex-direction控制默认是row也就是从左往右排改成column之后就变成从上往下排。剩下与之垂直的方向叫交叉轴row的交叉轴是垂直方向column的交叉轴是水平方向。这就是很多人嘴里说的“一维布局”。一维的意思是你在同一时刻只需要关心一个方向的排列规则。子项在主轴上的大小、间距、换行方式以及它们在交叉轴上的对齐方式都由容器的属性统一控制。看一个最简单的例子.container { display: flex; /* 主轴方向默认就是 row不写也行 */ } .item { flex: 1; }这个.container里的所有.item会像排队一样排在一条水平线上并且因为写了flex: 1它们会把容器的宽度均分掉。这种体验和传统的float布局完全不同不需要给每个元素手算百分比宽度也不需要额外清浮动因为子项之间天然由 flex 布局算法管理。但 Flex 的“一维”也意味着一个限制它不会主动帮你管理行和列组成的表格结构。虽然配合flex-wrap可以换行看起来好像变成了网格可换行后每一行的元素宽度、对齐规则依然是由主轴维度上的“流动”决定的而不是由“行轨道”和“列轨道”同时决定的。1.2 Grid 的行列与轨道思维先切格子再把元素放进去Grid 布局的中文名字叫网格布局它天生就是二维的。用 Grid 定义一个容器之后你首先要做的是用grid-template-columns和grid-template-rows把容器切成一个“横纵交错的表格”然后再把子项放进去。每个子项不仅可以占据一个格子还可以通过grid-column和grid-row跨列、跨行甚至可以重叠。我经常用一个类比来说明 Flex 和 Grid 的区别Flex 像排队所有人都站在同一条队伍里队伍的长度和每个人占的宽度都跟着内容走Grid 像砌墙先画好一块一块的砖位再把砖填进去格子的尺寸主要由你设定的轨道决定而不是完全由内容说了算。.grid { display: grid; grid-template-columns: 200px 1fr; grid-template-rows: auto 1fr; } .sidebar { grid-column: 1; grid-row: 1 / 3; background: #f5f5f5; } .content { grid-column: 2; grid-row: 1; }这个结构里左侧200px是固定侧边栏右侧1fr是自适应主区域.sidebar用grid-row: 1 / 3占满两行。这种写法在 Flex 里需要嵌套容器或者手动算高度才能做出来但在 Grid 里就是一次轨道定义加一次位置指定的事情。1.3 “内容优先”与“容器优先”两种布局哲学Flex 和 Grid 看似都叫布局但设计哲学很不一样。Flex 是内容优先的。它的flex-basis、flex-grow、flex-shrink都围绕子项的“原始尺寸”做文章。你放一段文字默认情况下这段文字自然有多宽它在主轴上的占位就从多宽开始之后再根据flex-grow和flex-shrink去分配剩余空间或收缩空间。所以用 Flex 做“内容多宽项就多宽”的组件特别顺手。Grid 是容器优先的。grid-template-columns: repeat(3, 1fr)一旦写好不管里面放三个元素还是三十个元素前三列都被轨道定死了。元素内容再多默认也只能在自己的格子内处理不能凭内容把手伸到旁边的格子里。这种特性让页面级的“大区块划分”异常稳定但用它做纯内容驱动的组件比如一个不固定数量的标签列表反而要费不少劲去调整grid-auto-rows和grid-auto-flow。我把这个差异整理成了一张表方便对照对比维度Flex 布局Grid 布局维度模型一维先定主轴再排子项二维同时定义行轨道和列轨道驱动方式内容优先子项尺寸影响轨道容器优先轨道尺寸约束子项典型场景导航、列表、按钮组、表单排列页面骨架、卡片墙、数据看板、复杂区域划分换行机制flex-wrap按内容流动换行轨道网格按行列自动放置重叠能力原生不支持要配合定位通过grid-row/grid-column轻松实现对齐控制主轴用justify-content交叉轴用align-items网格内用justify-items/align-items整体网格用justify-content/align-content其实两者不是“谁替代谁”的关系。Flex 解决的是“一排东西怎么摆”的问题Grid 解决的是“一整片区域怎么切”的问题。现实中的页面大部分是这两种问题的组合所以后面我会专门用一节聊嵌套配合。2. Flex布局常用属性从容器到子项的完整配置2.1 容器上的核心属性方向、对齐、换行与间距Flex 容器上的属性总共就那么几个但排列组合起来能覆盖大多数场景。首先是flex-direction它决定主轴方向。row是默认值元素从左到右row-reverse从右到左column从上到下column-reverse从下到上。移动端经常会在一屏内放几个入口图标图标加文字用column排图标一行、文字一行的效果就是靠这个属性做的。然后是justify-content它负责主轴方向的对齐方式可选值有flex-start、flex-end、center、space-between、space-around、space-evenly。其中space-between会把第一个子项贴到起点、最后一个子项贴到终点剩余空间全部变成子项之间的间隙做顶部分栏导航非常合适。space-evenly则是所有间隙完全相等包括首尾两端做按钮组时视觉效果最平均。交叉轴用align-items可选值包括stretch默认拉满交叉轴、flex-start顶部对齐、flex-end底部对齐、center垂直居中、baseline基线对齐。做一行表单时如果输入框高度不一致baseline能让文字底边对齐比手动调margin-top优雅得多。换行用flex-wrap默认是nowrap子项再多也硬挤在一行改成wrap后子项放不下会自动换到下一行。flex-flow是flex-direction和flex-wrap的缩写比如flex-flow: column wrap就代表从上往下排同时允许换行。间距用gap它和margin最大的区别是不会在容器边缘额外撑出空间只负责子项之间的缝隙。现在浏览器对gap的支持已经很成熟了F lex、Grid 里都能用。一个典型的垂直居中布局只要三行.center { display: flex; justify-content: center; align-items: center; }这是我平时写最多次的片段之一。别的方案要么用定位加位移要么用line-height碰运气都不如这个组合稳定。2.2 子项上的三大属性flex-grow、flex-shrink、flex-basisFlex 子项的灵活伸缩能力来自flex-grow、flex-shrink、flex-basis三个属性日常开发里基本都会用简写flex: 1或flex: 0 0 100px。flex-basis定义子项在主轴上的初始尺寸。默认是auto也就是以内容尺寸为基准也可以写成固定值比如flex-basis: 200px。flex-grow定义当主轴有剩余空间时子项按什么比例瓜分这份空间。默认0表示不放大。如果两个子项都设置了flex-grow: 1它们会各拿一半剩余空间如果一个是2一个是1拿到的剩余空间比就是 2:1。flex-shrink定义空间不足时子项如何收缩。默认1表示空间不够时允许等比例压缩。改成0后子项会拒绝收缩哪怕容器被撑破也不缩。这在做“固定宽度标签 自适应内容”的组合时特别有用标签设flex-shrink: 0内容区设flex: 1就不会出现标签被挤扁的情况。三个属性合起来的简写flex规则是flex: 1; /* flex: 1 1 0% */ flex: auto; /* flex: 1 1 auto */ flex: none; /* flex: 0 0 auto */ flex: 0 0 200px; /* 不放大、不缩小固定 200px */其中flex: 1和flex: auto的区别比较隐蔽。flex: 1会把flex-basis设成0%意思是“别管内容原本尺寸剩余空间从头开始全部重新分配”flex: auto则保留内容原始尺寸作为基础再分配剩余空间。三列自适应布局里我一般用flex: 1因为它不会因为某列内容特别长而破坏等分效果组件内部的可伸缩区域常用flex: auto这样文字多宽就先占多宽剩余空间再分给它。2.3 容易被忽略的 align-self、margin: auto 与 orderalign-self允许某个子项独立设置交叉轴对齐覆盖容器的align-items。比如容器里默认所有子项都是顶部对齐但最后一个按钮想单独底部对齐直接给这个按钮写align-self: flex-end就行不用为它单独包一个容器。margin: auto在 Flex 里是个隐藏神器。它不仅能居中还能“吸走”剩余空间。比如一个容器里有两个按钮想让左边按钮靠左、右边按钮靠右很多人会想到justify-content: space-between但其实也可以给右边按钮写margin-left: auto它的效果是把左侧剩余空间全部吸收把按钮推到最右。列表项里常见“左侧文字、右侧操作”的布局给操作按钮设margin-left: auto效果非常干净。order属性可以改变子项的视觉顺序默认都是0数字越大排得越靠后。虽然前端环节我通常不建议过度依赖order去重排 DOM因为屏幕阅读器的读取顺序还是按 DOM 走的但在某些组件内部比如把“确定”按钮在视觉上放到“取消”后面又不想改模板时用order救急很有效。3. Grid布局常用属性把页面当成一张可以操控的网格3.1 定义轨道grid-template-columns 与 grid-template-rowsGrid 的第一件事永远是划轨道。grid-template-columns定义列grid-template-rows定义行。这里的取值单位很多但核心就是几个px固定像素、fr比例单位、%百分比、auto内容自适应、minmax()最小最大范围以及repeat()简化重复写法。fr是 Grid 里最常用的比例单位它是“剩余空间比例”的缩写。和 Flex 的flex-grow思路类似1fr表示一份2fr表示两份。三列等分写作.grid { display: grid; grid-template-columns: repeat(3, 1fr); }repeat(3, 1fr)等效于1fr 1fr 1fr。如果左侧固定240px右侧自适应写作grid-template-columns: 240px 1fr。真正容易踩坑的是1fr和minmax(0, 1fr)。默认情况下1fr的最小尺寸是内容的min-content如果格子里的内容是一条很长的英文单词或者一张大图1fr轨道会被内容撑大导致另一列被挤小甚至溢出。minmax(0, 1fr)才是真正的“最小给 0最大分一份”它能强制轨道让位于内容收缩。这个问题在 Grid 里非常隐蔽后面第五节我会再展开讲。3.2 区域划分grid-template-areas 与 grid-area轨道划好之后元素怎么放最直观的方式是用grid-template-areas给网格区域命名然后用grid-area把子项放进对应区域。看一个后台布局.layout { display: grid; grid-template-columns: 220px 1fr; grid-template-rows: 60px 1fr; grid-template-areas: header header sidebar main; height: 100vh; } .header { grid-area: header; background: #333; } .sidebar { grid-area: sidebar; background: #eee; } .main { grid-area: main; background: #fff; }grid-template-areas里每一行字符串代表网格的一行每个名字代表一个单元格相同的名字拼在一起就形成一块区域。上面这个布局用三行 CSS 就完成了“顶部通栏 左侧边栏 右侧主区”的后台骨架不需要给子项写grid-row/grid-column去数数字。grid-template-areas还有一个好处响应式改版的时候非常直观。移动端要改成单列只需要在媒体查询里把区域模板换成.layout { grid-template-areas: header main sidebar; }汉字“移动端常见的头部、内容、底部三段式”就这么出来了。区域里的空格用.表示空单元格可以明确留白不会把元素错放进去。3.3 自动放置与隐式轨道grid-auto-flow 和 grid-auto-rowsGrid 并不要求你必须把每个子项的手动位置都写出来。你没指定位置的子项会自动按顺序填进格子里这个行为由grid-auto-flow控制。默认值是row也就是按行从左到右填一行填满再填下一行改成column就变成按列从上到下填。自动放置听着省事但有个细节如果子项数量超过了grid-template-columns和grid-template-rows定义的可见轨道数量Grid 会临时创建额外的“隐式轨道”来容纳这些元素。隐式轨道的尺寸默认是auto也就是按内容高度走。这时候如果你希望多出来的行保持固定高度比如每行80px就要显式设置grid-auto-rows: 80px。同理grid-auto-columns控制多余的列。一个很典型的场景是卡片墙只想定义列数不想管行数元素多了就往后铺。写法是.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); grid-auto-rows: 120px; gap: 16px; }这里的repeat(auto-fill, minmax(260px, 1fr))是一个非常有用的组合让网格根据容器宽度自动决定放几列每列最小 260px能多放一列就多放一列。加上grid-auto-rows: 120px所有自动生成的行都是等高整个卡片墙整齐得不像话。这个效果如果用 Flex 加手动计算百分比宽度遇到换行还要处理最后一行不满的情况很容易翻车。3.4 对齐控制items 和 content 两套体系Grid 的对齐属性分两套一套作用在“格子内部的子项”上也就是justify-items和align-items它们的简写是place-items: align-items justify-items另一套作用在“整个网格轨道相对于容器”上也就是justify-content和align-content简写是place-content。先记住这一点你要让某个元素在它自己的格子里居中用的是place-items: center。比如每个格子里有一张图片想让图片在格子内水平垂直居中就在容器上写.grid { display: grid; grid-template-columns: repeat(3, 1fr); place-items: center; }这个代码会让每个格子里的小元素都居中。如果你想让整个网格整体偏移到容器的右下角比如网格本身只有 600px 宽容器却有 1200px 宽这时用place-content: end center类似 Flex 里justify-content控制一整行元素的效果。很多初学者把这两套搞混在某个格子里写justify-content: center发现元素纹丝不动就是这个原因。justify-content控制的是轨道的位置不是子项的位置只有当你把容器宽度设得比轨道总宽度大它才产生视觉差异。4. 真实页面里的选型逻辑与组合方案4.1 一条线优先选 Flex导航、按钮组、列表项凡是元素本质上沿着一条线排列的我的选型经验是不需要犹豫直接 Flex。比如导航栏的菜单项无论数量怎么变它们的逻辑就是在一行里从左到右排列中间有间距最后一项可能要贴右边。Flex 的justify-content: space-between加gap能一行搞定。按钮组的逻辑也是一样要么等分要么紧凑排列Flex 天然适合。列表项的布局更典型。一个列表行里通常有头像、两行文字、右侧操作按钮这种“左侧内容 右侧操作”的结构文字部分又是一个小 Flex 容器设定flex-direction: column整体就是一个典型的嵌套 Flex。用 Flex 写列表因为主轴控制灵活多行省略号、文字溢出处理起来都顺手。4.2 整片区域优先选 Grid卡片墙、后台骨架、数据看板当页面需要同时控制行和列或者元素要组成规则的矩阵时Grid 的优势立刻显现。卡片墙是最好的例子。网上的卡片产品只要超过两列我都倾向于用grid-template-columns: repeat(auto-fill, minmax(240px, 1fr))因为它自动响应容器宽度卡片天生等高轨道高度由grid-auto-rows控制不需要像 Flex 那样考虑“最后一行少了几个”的视觉对齐问题。后台管理界面的骨架也是 Grid 的强项。顶部通栏、左侧菜单、右侧内容区再加上底部状态栏这种由“区块”构成的结构用grid-template-areas命名后不仅实现快可读性也好维护的人一眼就能看出页面是什么形状。数据看板里的各种统计卡本质上就是一个大 Grid 里套几个小 Grid因为每张卡片内部可能还有需要 Flex 处理的图例和数值行。4.3 嵌套组合外层 Grid 定骨架内层 Flex 排内容实际项目里用得最多的方案其实是 Grid 和 Flex 嵌套配合。Grid 负责“哪儿是哪儿”的区域划分Flex 负责“区域内部怎么排”。举个例子后台布局外层用 Grid 划三块区域顶部导航、左侧菜单、内容区。内容区内部某一块数据报表的卡片里卡片头部是“标题 操作按钮”用 Flex 排卡片主体里是一排统计指标每个指标又是一个小 Flex 容器。这样层次的嵌套把全局稳定和局部灵活兼顾了代码结构也清晰。常见的高阶写法是页面级骨架永远用 Grid组件内部永远优先考虑 Flex。骨架讲究稳定组件讲究内容自适应——两种布局正好各擅其长组合起来就是一套完整的前端布局方法论。4.4 一个综合案例从设计稿到最终代码的决策还原假设要做一个内容管理后台的某个页面顶部有面包屑和操作按钮下面有一个筛选条筛选条下面是卡片列表每张卡片里有图片、标题、标签和操作按钮。设计稿宽度 1440px卡片要自适应最少两列最多四列。我的实现思路分三步。第一步骨架用 Grid。grid-template-areas定义三行区域面包屑区、筛选区、卡片区列数直接一列到底因为这三块是堆叠关系。这里的 Grid 主要是为了把三个区域放在一个容器里后续如果要把筛选条和卡片区做成左右分栏只需要改区域模板不影响卡片内部结构。第二步筛选条用 Flex。筛选条里的字段数量不固定可能显示两三个也可能显示四五个用flex-wrap: wrap让它们放不下时自然换行gap: 12px控制间距。每个筛选项本身就是一个小的 Flex 容器标签和输入框上下排列用flex-direction: column。第三步卡片列表用 Grid。列定义写repeat(auto-fill, minmax(260px, 1fr))gap: 16px。卡片内部的图片用定宽显示标题用省略号标签用 Flex 排一行操作按钮用margin-left: auto推到右侧。到这里整个页面其实只用了两组网格属性、若干 Flex 属性没有任何一个手算的百分比宽度响应式也只是靠auto-fill自动完成。这个案例也说明了选型的判断顺序先看整体如果有明显的行和列区域用 Grid 切再看局部如果只是一条线上的内容排列用 Flex 排。这个判断顺序我用了很久基本没出过错。5. 实战中绕不开的坑几个让我返工最多的细节5.1 子项撑破容器flex-basis 与 min-width 的相互作用很多人在 Flex 布局里遇到“子项把容器撑爆”的情况第一反应是检查flex-shrink但我遇到过一堆代码里flex-shrink: 1写得好好的容器还是爆了。原因出在min-width的默认值上。Flex 子项的min-width默认是auto这意味着它的最小尺寸不会小于内容的min-content宽度。如果一个子项里放了一串不换行的英文或者一张固定宽度的图片哪怕flex-shrink允许收缩它也会因为min-width: auto拒绝收缩到内容最小宽度以下于是整个容器被撑出横向滚动条。解决办法是给子项设置min-width: 0让它在必要的时候可以收缩到比内容还小。这条经验在做横向滚动列表时特别关键。比如一个横向滚动的标签栏子项很多如果没写min-width: 0最后一个子项经常会莫名其妙被截掉或者把容器撑开。5.2 auto-fill 与 auto-fit 的诡异区别Grid 的repeat(auto-fill, minmax(...))和repeat(auto-fit, minmax(...))看起来很像但行为有细微差别。auto-fill会尽量创建更多轨道即使这些轨道里没有元素它们也会保留空间auto-fit则会把空轨道折叠掉让已有元素自动拉伸去填满剩余空间。简单说容器比较宽而元素只有几个的时候用auto-fill可能会出现右侧空出一块因为“轨道数量”是按最大列数创建的用auto-fit则是让这几个元素把整行分满。我一般做卡片墙用auto-fit希望卡片能撑满整行让视觉更饱满做日历或者需要固定列位的面板用auto-fill因为空缺位需要保留。这个坑最难受的地方在于两种写法在元素数量刚好等于最大列数时看不出任何区别只有减少元素数量时才会暴露。所以建议抽时间自己起一个 demo把容器宽度拖一拖亲眼看看两种行为的差异比死记结论可靠。5.3 Grid 里的 1fr 不等于“最小 0”minmax 才是正解前面提到过1fr的默认最小尺寸是内容的最小尺寸这一点在 Grid 里制造了很多诡异现象。最常见的是左右两栏布局左栏1fr右栏1fr按理说应该各占一半。可如果左栏里有个很长的表格数字或者一个带white-space: nowrap的元素这一栏会被撑宽右栏被挤得特别窄甚至内容溢出到容器外。正确的写法是把1fr替换成minmax(0, 1fr)。minmax(0, 1fr)明确告诉浏览器最小轨道尺寸是 0最大轨道尺寸是一份剩余空间。这样轨道下限被放开内容再长也只会溢出自身格子而不是把整列撑破。我后来写 Grid 列的时候几乎默认都用minmax(0, 1fr)代替裸1fr除非有意识地让内容决定最小宽度。5.4 align-content 不生效只有多行或多列时才看得见align-content和justify-content在 Grid 和 Flex 里的生效条件不一样。在 Flex 里如果容器是单行布局没有wrapalign-content基本没有作用因为只有一根线没有多根线可排这时你要用align-items去控制子项在交叉轴的位置。在 Grid 里如果容器高度和轨道总高度一样align-content也不会产生位移因为没有任何剩余空间可分。我见过不少新手在一个固定高度的 Grid 容器里写align-content: center发现布局没变化以为浏览器出问题。其实只要容器高度被内容撑满了剩余空间为 0自然不会移动。这个属性要在“容器明显高于所有轨道总高度”的前提下才有意义。同理justify-content要在容器宽度大于轨道总宽度时才会生效。调试的时候先给容器一个背景色把高度或宽度放宽一点就能立刻看出剩余空间到底存不存在。5.5 长短内容混排时的等高问题Flex 的默认行为是让同一行的子项高度拉伸一致适合做等高列表但如果某个子项内容特别长它会把整行撑高旁边的子项也跟着变高这个“等高”其实是“等拉伸高度”不是等内容高度。Grid 的轨道高度则更可控你可以给行轨道定义固定的auto或minmax让高内容溢出而不是撑开轨道。具体到卡片墙如果卡片里文字长短不一我建议用 Grid 的grid-auto-rows: minmax(160px, auto)来控制行高下限让卡片至少有 160px 高内容多时再自然增高。这样比 Flex 里各种对齐微调要省心也让相邻的卡片即使内容不同也能保持视觉上的整齐。我自己现在的习惯是凡是元素沿着一个方向排、尺寸需要随内容变化默认先写display: flex凡是需要同时控制行和列、整体要填满容器的版面直接上 Grid。这个习惯帮我在多数页面里少走了弯路也让我在接手别人的老代码时能通过布局方式快速猜出作者当时的设计意图。Flex 和 Grid 各自都不是万能工具但把它们的边界想清楚、属性用熟之后大部分页面布局问题在我眼里就只剩两种解法了排一条线还是切一张网。
返回列表