Grid布局:学习网格系统,构建二维复杂布局

说实话,刚接触前端布局那会儿,我总觉得Flexbox已经够用了。直到有一次做后台管理系统,要排一个复杂的仪表盘——图表、数据卡片、列表混在一起,用Flexbox嵌套了四五层,改一个地方全崩了。嗯,那时候我才意识到,二维布局这事儿,得靠Grid。

Grid到底是什么?

Grid布局,说白了就是给页面画格子。你把容器分成行和列,然后把元素往格子里一放,完事。跟Flexbox最大的区别是:Flex是一维的,要么横着排要么竖着排;Grid是二维的,横竖一起管。

核心概念就三个:

  • 网格容器——你设了 display: grid 的那个元素
  • 网格项——容器里的直接子元素
  • 网格线——划分行和列的线,从1开始编号

我在项目中遇到过一位同事,用Grid做表格布局,结果发现元素对不齐。后来一看,他把网格线编号搞混了。记住:网格线是从左上角开始数的,不是从0。

基础用法:三步上手

先看个最简单的例子。假设你要做一个三列等宽的布局:

.container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 20px;
}

这里 1fr 是Grid特有的单位,代表「剩余空间的一份」。三个1fr就是三等分。你想想看,如果用百分比,还得算边框和内边距,多麻烦。

行的话用 grid-template-rows,用法一样。不过有个小技巧:如果你不确定有多少行,可以用 auto,让内容自己撑开。

我的习惯:列宽用fr,行高用auto。除非你明确知道每行的高度,否则别写死。

二维布局的威力

Grid真正厉害的地方,是能同时控制行和列。来看一个典型的页面骨架:

.layout {
  display: grid;
  grid-template-columns: 200px 1fr;
  grid-template-rows: 60px 1fr 40px;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  height: 100vh;
}

.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }

看到 grid-template-areas 了吗?这是我最喜欢的功能。直接用字符串画布局,哪里是头部、哪里是侧边栏,一目了然。我曾经用这个给一个电商后台重构布局,原来500行的CSS缩到了80行。

网格项的定位

有时候你需要某个元素跨多行或多列。比如一个图片要占两列:

.featured-image {
  grid-column: span 2;
  /* 或者指定起止线:grid-column: 1 / 3; */
}

这里要注意:span 2 表示跨越2列,而 1 / 3 表示从第1条线到第3条线。效果一样,但写法不同。我个人习惯用 span,因为更直观——「我要占两格」。

我曾经踩过的坑:用 grid-column: 1 / 3 时,如果网格只有2列,那没问题。但如果后来改成3列,这个元素就只占前两列了。用 span 的话,它会自动适应。所以,除非你有明确的线编号需求,否则优先用 span。

响应式设计:自动填充与自适应

Grid做响应式特别顺手。比如你要做一个卡片列表,桌面端4列,平板2列,手机1列。传统做法得写三个媒体查询。用Grid的话:

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 16px;
}

这一行代码就搞定了。auto-fill 会自动计算能放几列,minmax(250px, 1fr) 保证每列至少250px,多了就均分。你想想看,这比写媒体查询省了多少事?

不过有个细节:auto-fill 和 auto-fit 的区别。前者会保留空列,后者会折叠空列。如果你希望卡片始终靠左对齐,用 auto-fit。

对齐与间距

Grid的对齐分两个维度:

  • 容器内对齐:justify-items(水平)、align-items(垂直)
  • 网格项自身对齐:justify-self、align-self

举个例子,你想让所有网格项居中:

.container {
  justify-items: center;
  align-items: center;
}

如果只想让某个按钮靠右:

.button {
  justify-self: end;
}

间距用 gap 属性,它是 row-gap 和 column-gap 的简写。我建议统一用 gap,除非你行间距和列间距不一样。

性能调优:别让Grid拖慢页面

Grid本身性能很好,但用不对也会出问题。我总结了几条经验:

场景 建议 原因
大量网格项(100+) 用 grid-auto-flow: row dense 配合虚拟滚动 避免一次性渲染所有元素
嵌套Grid 最多嵌套3层 超过3层,浏览器重排成本剧增
动态改变网格结构 用CSS类切换,别直接操作style 类切换触发重排,但比style操作高效
图片在Grid中 给图片设 width: 100%; height: auto 防止图片撑破网格,导致布局抖动

避坑指南:我曾经在一个数据大屏项目里,用Grid排了200多个图表卡片。结果页面加载要3秒。后来发现是每个卡片都用了 grid-template-rows: 1fr,导致浏览器要反复计算行高。改成 auto 后,加载时间降到了0.8秒。

实战:一个完整的仪表盘布局

来,咱们把前面学的串起来。做一个四区域的仪表盘:

.dashboard {
  display: grid;
  grid-template-columns: 280px 1fr 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "sidebar header header"
    "sidebar main   aside"
    "sidebar footer footer";
  gap: 12px;
  min-height: 100vh;
  padding: 16px;
}

/* 响应式:小于768px时变单列 */
@media (max-width: 768px) {
  .dashboard {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "main"
      "aside"
      "sidebar"
      "footer";
  }
}

你看,一个媒体查询就搞定了从三列到单列的切换。如果用Flexbox,你得改HTML结构或者写一堆覆盖样式。

知识体系总览

下面这张图,是我自己总结的Grid知识框架。每次做新项目前,我都会扫一眼:

Grid布局 容器属性 grid-template-columns grid-template-rows grid-template-areas gap / justify-items 项目属性 grid-column / grid-row grid-area justify-self / align-self 单位与函数 fr / minmax() repeat() / auto-fill auto-fit / min-content 性能调优 避免深层嵌套 使用类切换 图片尺寸控制 二维布局,一劳永逸

这张图把Grid分成了四个模块:容器属性、项目属性、单位与函数、性能调优。每次做布局前,先想清楚你要用哪个模块的能力。比如要做响应式,重点看「单位与函数」;要做复杂区域划分,重点看「容器属性」。

写在最后

Grid布局学起来不难,但用好需要练习。我的建议是:从今天开始,所有新项目的布局都用Grid写。哪怕只是一个简单的两栏,也试试Grid。用上一个月,你就回不去Flexbox了。

记住一句话:Flexbox管一维,Grid管二维。选对工具,事半功倍。

可可精品资料库