- 文档
- 技术博客
- 教程
【免费下载链接】weekly
前端精读周刊。帮你理解最前沿、实用的技术。
本文基于前端精读周刊第 124 期(前沿技术/124.精读《用 css grid 重新思考布局》.md)展开。CSS Grid 不止是一种新的排版手段,更是一次布局思维范式的升级:它将布局控制从层层嵌套的 Div 中解放出来,收拢到父容器的一纸二维描述里。读完本文,你将掌握
grid-template-areas平铺式布局的完整写法与响应式重组技巧,理解"布局与 DOM 分离"对组件模块化组织的深远影响,并能在实际项目中正确地在 Grid 与 Flex 之间做技术选型。
引言:从一维到二维的布局跃迁
Flex 与 Grid 相比,就像功能键盘和触摸屏。功能键盘只能上下左右地控制光标(x、y 轴),而触摸屏打破了布局障碍,直接以二维平面(z 轴)触达目标——无论 UI 内部布局再复杂,都可以通过直接定位来描述。
Flex 是一维布局方式,为了形成复杂结构,我们不得不不断嵌套 Div;而一旦布局发生变更,原有嵌套结构如果不能"兼容变化"到新结构,代码就需要重构。Grid 则像触摸屏一样支持二维布局,即便布局方式做了翻天覆地的调整,也仅需少量修改即可适配。这正是本期精读要解决的问题:理解这个革命性布局技术给布局组织,甚至代码逻辑组织方式带来的变化。
一维布局的通病:Div 层级复杂与定制能力弱
原文作者首先抛出 Flex 的问题,这其实是block、float、flex三种布局模式的通病:
- 布局结构由 Div 层级结构描述,导致 Div 层级复杂,且遇到结构变更时难以维护;
- 定制能力弱。Flex 布局有一些不受控制的智能设定,比如宽度 50% 的子元素会被同级元素挤到 50% 以下。这种智能化在某些场景是需要的,但由于没有提供像 Grid 的
minmax之类的 API,定制型不足。
以一张个人名片卡片为例,用 Flex 描述它的 DOM 结构时,HTML 的嵌套层级直接决定了视觉分区:
<div class="card"> <div class="profile-sidebar"> <img src="https://i.pravatar.cc/125?image=3" alt="" class="profile-img" /> <ul class="social-list"> <li> <a href="#" class="social-link" ><i class="fab fa-dribbble-square"></i ></a> </li> <li> <a href="#" class="social-link" ><i class="fab fa-facebook-square"></i ></a> </li> <li> <a href="#" class="social-link" ><i class="fab fa-twitter-square"></i ></a> </li> </ul> </div> <div class="profile-body"> <h2 class="profile-name">Ramsey Harper</h2> <p class="profile-position">Graphic Designer</p> <p class="profile-info"> Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere a tempore, dignissimos odit accusantium repellat quidem, sit molestias dolorum placeat quas debitis ipsum esse rerum? </p> </div> </div>利用 HTML 嵌套结构,我们把图形纵向分成两大块(profile-sidebar与profile-body),然后在每块内部继续嵌套划分布局——这是最经典的布局行为。
对应的样式文件需要对每一层布局进行描述,同时支持多分辨率弹性布局,包括顶层card容器在内的一些样式都需要做调整:
.card { width: 80%; margin: 0 auto; display: flex; flex-direction: column; max-width: 600px; background: #005e9b; flex-basis: 250px; color: white; padding: 2em; text-align: center; } .profile-info { font-weight: 300; opacity: 0.7; } .profile-sidebar { margin-right: 2em; text-align: center; } .profile-name { letter-spacing: 1px; font-size: 2rem; margin: 0.75em 0 0; line-height: 1; } .profile-name::after { content: ""; display: block; width: 2em; height: 1px; background: #5bcbf0; margin: 0.5em auto 0.65em; opacity: 0.25; } .profile-position { text-transform: uppercase; font-size: 0.875rem; letter-spacing: 3px; margin: 0 0 2em; line-height: 1; color: #5bcbf0; } .profile-img { max-width: 100%; border-radius: 50%; border: 2px solid white; } .social-list { list-style: none; justify-content: space-evenly; display: flex; min-width: 125px; max-width: 175px; margin: 0 auto; padding: 0; } .social-link { color: #5bcbf0; opacity: 0.5; } .social-link:hover, .social-link:focus { opacity: 1; } .bio { padding: 2em; display: flex; flex-direction: column; justify-content: center; } @media (min-width: 450px) { .bio { text-align: left; max-width: 350px; } } .bio-title { color: #0090d1; font-size: 1.25rem; letter-spacing: 1px; text-transform: uppercase; line-height: 1; margin: 0; } .bio-body { color: #555; } .profile { display: flex; align-items: flex-start; } @media (min-width: 450px) { .card { flex-direction: row; text-align: left; } .profile-name::after { margin-left: 0; } }可以看到,一维布局把"布局结构"写死在 DOM 层级里:视觉上"左边是头像社交区、右边是文字区"这种二维关系,要靠profile-sidebar+profile-body两个容器才能表达;要切换横竖排布,还必须在@media里同时修改多个容器的多项属性(card的flex-direction、.profile-name::after的margin-left等)才能联动生效。
Grid 的核心 API:用 grid-template-areas 平铺描述布局
Grid 有许多 API,原文重点聚焦grid-template-areas这个属性。利用它,我们可以不关心模块的 HTML 结构,直接以平铺方式描述布局。先看平铺后的 HTML——所有区块都是card的直接子元素,不再有sidebar/body之类的分组容器:
<div class="card"> <img src="https://i.pravatar.cc/125?image=3" alt="" class="profile-img" /> <ul class="social-list"> <li> <a href="#" class="social-link"><i class="fab fa-dribbble-square"></i></a> </li> <li> <a href="#" class="social-link"><i class="fab fa-facebook-square"></i></a> </li> <li> <a href="#" class="social-link"><i class="fab fa-twitter-square"></i></a> </li> </ul> <h2 class="profile-name">Ramsey Harper</h2> <p class="profile-position">Graphic Designer</p> <p class="profile-info"> Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere a tempore, dignissimos odit accusantium repellat quidem, sit molestias dolorum placeat quas debitis ipsum esse rerum? </p> </div>可以看到,使用 Grid 可以将 UI 结构与 HTML 结构分离:HTML 结构仅描述包含关系,我们只需在样式文件中描述具体 UI 结构。样式文件只截取 Grid 相关部分:
.card { width: 80%; margin: 0 auto; display: flex; flex-direction: column; max-width: 600px; background: #005e9b; flex-basis: 250px; color: white; padding: 2em; text-align: left; display: grid; grid-template-columns: 1fr 3fr; grid-column-gap: 2em; grid-template-areas: "image name" "image position" "social description"; } .profile-name { grid-area: name; } .profile-position { grid-area: position; } .profile-info { grid-area: description; } .profile-img { grid-area: image; } .social-list { grid-area: social; }这段代码里几个关键点值得展开:
grid-template-columns: 1fr 3fr定义两列,比例为 1:3。fr是 Grid 的弹性尺寸单位,按比例瓜分剩余空间,效果类似 Flex 中的伸缩因子,但语义更可控;grid-column-gap: 2em(现代等价写法为column-gap: 2em)定义列间距,配合grid-row-gap(row-gap)可以精确控制网格沟槽;grid-template-areas用 3 行 × 2 列的字符串矩阵描述布局,每个单元格中的标识符(如image、name)通过子元素的grid-area: xxx与之绑定;- 同一标识符可以横跨多个单元格:
image同时占据第一列的第一、二行,因此头像区域自动纵向贯通两行,形成"左侧一列、右侧上中下三块"的二维格局。
grid-template-areas是进一步抽象的语法,它将页面结构通过直观的文本描述出来,无论是理解还是修改都更为轻松。
响应式适配:只需重组模板字符串
这种描述方式在适配不同分辨率时也具有天然优势——只要重组grid-template-areas即可:
@media (min-width: 600px) { .card { text-align: left; grid-template-columns: 1fr 3fr; grid-template-areas: "image name" "image position" "social description"; } }对比前面的 Flex 版本:Flex 需要在媒体查询里同时调整card的flex-direction、子容器的对齐方式等多项样式;而 Grid 版本只需要重新声明grid-template-areas的矩阵结构,子元素会自动跟随grid-area名称重新就位。归根结底,Grid 通过二维结构描述,把子元素的布局控制收到了父级,使布局描述更加直观。
选型结论:Grid 与 Flex 各司其职
原文作者也明确指出,Flex 依然有使用场景:简单的一维结构,或者space-between等 Flex 独有语法的情况。因此推荐:整体、复杂的二维布局采用 Grid,一维的简单布局采用 Flex。
抽象,再抽象:布局描述从 DOM 到字符串再到可视化
Grid 的布局思路给了我们很多启发:HTML 结构与 UI 结构的分离有助于减少 Div 的层级,使代码看上去更清晰。也许有人会疑惑:Grid 无非将 HTML 布局部分的功能挪到了 CSS,整体复杂度应该不变。其实,从grid-template-areas这个 API 可以看到,Grid 不仅仅将布局功能抽到 CSS 中,更是将布局描述进行了一层抽象,使代码更易维护。
为什么 Grid 可以对布局进行抽象
因为 Grid 将二维结构都掌握在手中,得到了更大的布局能力,才能进一步把结构化的语法抽象为字符串描述。抽象的好处是不言而喻的——一堆嵌套的 Div 与下面这段代码,哪个更易读呢?
.card { grid-template-areas: "image name" "image position" "social description"; }一般来说,代码抽象程度越高就越易读、越易维护。
可视化是对文本的再抽象,同时规避非法布局
在文本抽象之上,还可以借助可视化工具把 Grid 渲染出来,并以 UI 方式直接拖拽调整。UI 是对文本的再抽象,同时可以规避一些不可能存在的语法,比如下面这种非法描述:
.card { grid-template-areas: "image name" "image position" "social image"; }布局只能以凸多边形方式拓展:一个区域可以纵向合并、横向合并、形成矩形或 L 形,但不可能分离成两块,也不可能突然插入其他模块而变成凹多边形。因此可视化工具可以将这类错误规避,并简化为横竖多条线的方式对 UI 进行划分——这种描述方式效率更高。Grid 以及图形化插件的探索,是布局领域的一大进步,是不断抽象的尝试,要解决的问题只有一个:如何提供一种更直观的描述 UI 的方式。
布局对模块化的影响:让 JS 模块从 UI 视角回归逻辑视角
Grid 将布局方式提高了一个维度,会直接影响到 JS 的模块化方式。尤其是以 JSX 组织代码的情况下,一个模块等于 UI + JS,通过嵌套方式的布局会让我们更倾向于站在 UI 视角划分模块。
举个例子,假设页面上有 A~E 共 5 个内容模块,它们的二维排布关系如下:
- 左侧一列:A 在上、B 在下;
- 右侧两列:C 横跨顶部,D、E 在下方左右并排。
如果用 Flex 方式布局,我们可能会首先创建模块 X 作为左侧容器(子元素是 A 和 B),创建模块 Y 作为右侧容器(子元素是 C 以及新容器 Z),Z 容器的子元素是 D 和 E。如果你的第一印象是这么组织代码,不得不承认模块化会受到布局方式的影响。虽然许多时候这样划分是正确的,但当这 5 个模块各自没有关联时,我们创建的容器 X、Y、Z 就失去了复用性,在新的组合场景里又得重新组合一遍。
但在 Grid 语法中,我们不需要 X、Y、Z 这三层容器,只需要用可视化生成器按照布局拖拽,即可自动生成如下代码:
.parent { display: grid; grid-template-columns: 3fr repeat(2, 1fr); grid-template-rows: repeat(5, 1fr); grid-column-gap: 0px; grid-row-gap: 0px; } .div1 { grid-area: 1 / 1 / 3 / 2; } .div2 { grid-area: 3 / 1 / 6 / 2; } .div3 { grid-area: 1 / 2 / 2 / 4; } .div4 { grid-area: 2 / 2 / 6 / 3; } .div5 { grid-area: 2 / 3 / 6 / 4; }这里grid-area: 1 / 1 / 3 / 2是"行起 / 列起 / 行止 / 列止"的简写定位语法(也可以写成grid-row: 1 / 3; grid-column: 1 / 2更直观),配合repeat(2, 1fr)等模板语法,5 个模块被精确地钉在网格坐标系上。
其实grid-template-columns+grid-template-rows组合起来使用比grid-template-areas更强大,但纯代码方式描述没有grid-template-areas直观;而一旦配合可视化系统,这种"按坐标钉格子"的方式就非常直观了。将 A~E 这 5 个模块的布局关系抽出来后,它们之间的关系就打平了,我们可以完全从逻辑视角审视如何做模块化——模块之间是否有关联、是否需要复用,都由业务语义决定,而不是由视觉嵌套决定。
仓库印证:布局抽象在周刊知识体系中的延伸
这种"布局与 DOM 分离"的思路,在本仓库其他精读中有着清晰的呼应,可以作为进一步阅读的路径:
- 磁贴布局三部曲(功能实现、性能优化):磁贴布局以"格子坐标系"管理组件位置,本质上是把 Grid 的二维网格思想工程化——组件只关心自己落在哪个行列区间,容器只负责描述网格结构,这正是
grid-template-areas/grid-area定位哲学的实践延伸; - keepAlive 模式:文中明确提到"自由、磁贴、流式布局都可以通过拖拽轻松改变组件父元素",而 keepAlive 方案通过
createPortal实现DOM 结构与 React 实例结构分离——这与"布局结构与 DOM 结构分离"是同一思维在不同层面的复现,且文末建议"尽量采用绝对定位的布局策略"来规避新增 DOM 嵌套带来的定位问题,也印证了二维/绝对定位式布局在复杂场景下的优势; - State of CSS 2022中介绍的subgrid能力:解决 grid 嵌套 grid 时子网格轨迹线无法对齐父网格的问题,只需在子网格声明
grid-template-columns: subgrid; grid-template-rows: subgrid;即可让子网格继承父网格的轨道定义——这是对"网格抽象"的进一步深化,让嵌套场景也能保持坐标系的一致性。
这些内容从不同侧面证明了同一结论:把布局从内容结构中剥离、用更高维度的抽象来描述,是前端布局发展的主旋律。
总结:布局与 DOM 分离的前端梦想
CSS Grid 本质上是一种二维布局的语法,相比 Block、Flex 等一维布局方案,多了一个维度,可以同时从行与列角度定义布局,因此派生出grid-template-areas等语法,整体上更内聚、更直观,抽象度也更高了。
理解了这些,也就理解了布局未来的发展方向:让布局与 DOM 分离一直是前端的一个梦想。开发 UI 部分时,只需关心页面由哪些模块组成、去实现这些模块就行了,而不需要关心模块之间应该如何组合。在描述组合时,可以通过可视化或比较抽象的字符串描述布局的结构,并对应到写好的模块上——这样的代码维护性远高于用 Div 描述结构的方案。
实际项目中可以按如下准则落地:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 整体页面骨架、复杂二维排布 | Grid(grid-template-areas) | 布局描述收拢到父级,文本直观,响应式只需重组模板字符串 |
| 局部一维排列(导航、按钮组、图标列表) | Flex | 简单直接,且space-between、space-evenly等语法是 Flex 独有 |
| 组件间无关联的复杂组合 | Grid + 可视化生成器 | 消除 X/Y/Z 式冗余容器,模块关系打平,回归逻辑视角划分模块 |
| 网格嵌套需对齐轨道 | Grid + subgrid | 子网格继承父网格轨道,保持坐标系一致(详见 State of CSS 2022) |
本文依据仓库文档 前沿技术/124.精读《用 css grid 重新思考布局》.md 编写,文中所有代码示例均继承自原文并补充了参数语义说明;延伸阅读路径见本仓库 readme.md 的周刊目录。
- 文档
- 技术博客
- 教程
【免费下载链接】weekly
前端精读周刊。帮你理解最前沿、实用的技术。
相关推荐
终极CSS Grid嵌套布局指南:从零掌握复杂组件设计的完整攻略
终极CSS Grid嵌套布局指南:从零掌握复杂组件设计的完整攻略 CSS Grid布局是现代前端开发中构建复杂界面的强大工具,尤其在处理嵌套结构时展现出卓越的灵
教程示例工程使用 CSS Grid 实现二维布局:Front-End-Checklist 规则实战指南
使用 CSS Grid 实现二维布局:Front End Checklist 规则实战指南 CSS Grid 是 CSS 中最强大的二维布局系统,它专门解决"同
freeCodeCamp 课程源码解析:用嵌套 CSS Grid 构建「网格中网格」布局
freeCodeCamp 课程源码解析:用嵌套 CSS Grid 构建「网格中网格」布局 本篇以 freeCodeCamp 响应式设计课程中「Create Gr
前端后端教育
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考