在网页排版中,多列布局是最基础也最容易出问题的需求之一。早期我们依赖浮动或者表格,后来有了inline-block,但这些方案在响应式与对齐控制上都不够直观。CSS提供了两套现代布局机制:Flexbox与Grid。它们并非互斥,合理结合可以覆盖绝大多数多列场景。

一、Flexbox与Grid的核心差异
Flexbox是一维布局模型,它只关注主轴与交叉轴方向,适合处理“一行或一列内元素的排列、伸缩、对齐”。比如导航菜单、卡片列表,用Flex可以轻松让子项等宽、居中或两端对齐。它的优势在于内容驱动:子元素大小不确定时,Flex能自动分配剩余空间。
Grid是二维布局模型,同时定义行与列,适合整体页面框架。你可以用grid-template-columns直接写出三栏结构,并精确控制每栏占比与间距。Grid的强项是结构驱动:先画好棋盘,再把内容放进去。当布局需要同时在横向与纵向对齐时,Grid比Flex更不容易写出嵌套地狱。
理解这一点后,常见误区就清晰了:有人用Flex硬拼整页框架,导致媒体查询写满屏;也有人用Grid排卡片流,反而比Flex更啰嗦。正确思路是“Grid管大局,Flex管子项”。
二、Grid搭建多列外层框架
我们以一个典型后台界面为例:左侧固定导航,右侧主体内容分两列显示卡片。用Grid只需几行就能定好骨架。下面的示例展示了如何用grid-template-columns划分列,并用gap统一间距。
/* 外层容器使用Grid定义整体多列布局 */
.layout {
display: grid;
/* 左侧260px固定,右侧自动填充剩余空间 */
grid-template-columns: 260px 1fr;
gap: 16px;
}
/* 右侧内容区再划分为两列卡片区 */
.content {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
上述代码中,最外层.layout把页面分成导航与内容两大列;.content作为Grid子项,自身也是Grid,进一步把内容拆成两列。这种“Grid嵌套Grid”的做法比用div加float清晰得多,且不需要清除浮动。
在窄屏设备上,只需一条媒体查询改变grid-template-columns即可让导航栏移到上方,内容区变单列。相比Flex方案里对每一个子项单独设置百分比,Grid的改动集中在容器上,维护更方便。
三、Flex处理内层卡片排列
虽然Grid能排卡片,但若卡片内部有“图标靠左、文字靠右、底部按钮贴底”的需求,继续用Grid会增加复杂度。此时在卡片内部切到Flex更自然。下面代码演示卡片自身用Flex做纵向排列,并保证按钮始终在底部。
<div class="card"> <h3>标题</h3> <p class="desc">一些描述文字,长度不固定。</p> <button class="action">查看</button> </div>
.card {
display: flex;
flex-direction: column;
border: 1px solid #ddd;
padding: 12px;
}
.card .desc {
flex: 1; /* 描述区拉伸,把按钮挤到最底 */
}
.action {
align-self: flex-start;
}
这里flex-direction: column让卡片内容纵向排,flex: 1赋给描述段落,使其吸收多余高度,从而不同长度的卡片放在同一Grid单元格里也能底部对齐。若纯用Grid,需要定义grid-template-rows: auto 1fr auto,写法并不比Flex简单。
另外Flex的align-self可单独控制某个子项交叉轴位置,比如按钮不撑满而是左对齐。这种细粒度控制在组合布局中非常实用,避免了为微小差异额外套一层容器。
四、结合实践的完整示例
把前面两点合并,就是一个真实可用的多列布局。外层Grid分栏,内容区Grid分列,卡片内Flex排版。以下给出简化但完整的页面代码。
<div class="layout">
<aside class="sidebar">导航</aside>
<section class="content">
<div class="card">
<h3>模块A</h3>
<p class="desc">内容较短</p>
<button class="action">进入</button>
</div>
<div class="card">
<h3>模块B</h3>
<p class="desc">内容较长,用来测试卡片等高与按钮贴底效果。</p>
<button class="action">进入</button>
</div>
</section>
</div>
.layout {
display: grid;
grid-template-columns: 260px 1fr;
gap: 16px;
font-family: sans-serif;
}
.sidebar {
background: #f5f5f5;
padding: 16px;
}
.content {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.card {
display: flex;
flex-direction: column;
border: 1px solid #ddd;
padding: 12px;
background: #fff;
}
.card .desc {
flex: 1;
margin: 8px 0;
}
.action {
align-self: flex-start;
padding: 6px 12px;
}
这段代码直接在浏览器运行就能看到:左侧导航固定,右侧两张卡片等高,按钮贴底且左对齐。如果窗口变窄,给.layout加一个@media (max-width: 720px)把grid-template-columns改成1fr,导航就会自动掉到内容上方,无需调整卡片内部的Flex。
从工程角度看,这种组合把“结构”与“内容排列”解耦:设计师改栏数只动Grid,开发改卡片样式只动Flex,两者互不干扰。比起早年用float加clearfix,代码量更少,bug也更少。
五、常见坑与建议
第一,不要给Grid容器里的直接子项再无必要地套Flex父级。如果仅仅是等宽并列,Grid的repeat()已足够,硬套Flex反而增加层级。第二,Flex的默认min-width: auto会导致内容过长时溢出,可在Flex子项加min-width: 0修复。
第三,响应式断点尽量写在Grid容器上,而不是每个卡片。这样维护者一眼能看清布局变化。最后建议用浏览器开发者工具分别给容器加描边,确认哪些是Grid区域、哪些是Flex区域,避免脑海中混淆两套坐标系。
掌握Flex与Grid的分工,多列布局就不再是拼凑而是设计。下次遇到后台页面、商品列表或文档目录,先画Grid大框,再用Flex填细节,效率与可读性都会明显提升。