Skip to content

Grid 项目属性:精确控制布局中的每个元素

想象你在玩拼图游戏,游戏板给你提供了一个网格结构(这就是 Grid 容器),而每一块拼图(Grid 项目)可以占据一个或多个格子,可以放在任何位置。Grid 项目属性就是你的工具,让你可以精确地告诉每一块拼图:"你应该从第 2 行第 3 列开始,占据 2 行 3 列的空间"。

在上一章中,我们学习了如何用容器属性定义网格的整体结构。现在,让我们学习如何控制单个项目在这个结构中的位置和表现。

基于网格线的定位

Grid 最强大的特性之一是可以精确地指定项目应该占据哪些单元格。这是通过网格线编号实现的。

grid-column:控制列位置

grid-column 属性指定项目在列方向上的起始和结束位置。

基础用法

css
.item {
  grid-column-start: 1; /* 从第 1 条垂直线开始 */
  grid-column-end: 3; /* 到第 3 条垂直线结束 */
  /* 相当于占据第 1 列和第 2 列 */
}

更常用的是简写形式:

css
.item {
  grid-column: 1 / 3; /* 从线 1 到线 3 */
}

让我们看一个完整的例子:

html
<div class="grid">
  <div class="item item-1">Item 1</div>
  <div class="item item-2">Item 2</div>
  <div class="item item-3">Item 3</div>
</div>
css
.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr); /* 4 列,有 5 条垂直线 */
  grid-auto-rows: 100px;
  gap: 10px;
  background-color: #f5f5f5;
  padding: 10px;
}

.item {
  background-color: #2196f3;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
}

.item-1 {
  grid-column: 1 / 3; /* 占据第 1-2 列 */
  background-color: #f44336;
}

.item-2 {
  grid-column: 3 / 5; /* 占据第 3-4 列 */
  background-color: #4caf50;
}

.item-3 {
  grid-column: 1 / 5; /* 占据所有 4 列 */
  background-color: #ff9800;
}

在这个例子中:

  • Item 1 横跨 2 列(从线 1 到线 3)
  • Item 2 也横跨 2 列(从线 3 到线 5)
  • Item 3 横跨所有 4 列(从线 1 到线 5)

使用 span 关键字

有时候你不关心具体从哪条线开始,只想让项目占据几列。这时可以使用 span 关键字:

css
.item {
  grid-column: span 2; /* 占据 2 列,具体位置由自动放置决定 */
}

你也可以指定起始线,然后用 span 说明要跨越几列:

css
.item {
  grid-column: 2 / span 3; /* 从线 2 开始,跨越 3 列,到线 5 */
}

或者反过来,指定结束线和跨越的列数:

css
.item {
  grid-column: span 2 / 5; /* 跨越 2 列,到线 5,从线 3 开始 */
}

实际应用:创建特色项目

css
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 20px;
}

.card {
  background-color: white;
  border: 1px solid #ddd;
  padding: 20px;
  border-radius: 8px;
}

.card-featured {
  grid-column: span 2; /* 特色卡片占据 2 列 */
  background-color: #2196f3;
  color: white;
  font-size: 20px;
}

负数索引

Grid 支持负数索引,用于从末尾开始计数。-1 表示最后一条线,-2 表示倒数第二条线,以此类推。

css
.item {
  grid-column: 1 / -1; /* 从第一条线到最后一条线,占据所有列 */
}

这在你不确定网格有多少列时特别有用:

css
.header {
  grid-column: 1 / -1; /* 无论有多少列,都占据所有列 */
}

grid-row:控制行位置

grid-row 的用法与 grid-column 完全相同,只是它控制的是行而不是列。

css
.item {
  grid-row: 1 / 3; /* 从第 1 行到第 3 行,占据 2 行 */
}

完整示例:创建复杂布局

html
<div class="grid">
  <div class="item header">Header</div>
  <div class="item sidebar">Sidebar</div>
  <div class="item main">Main Content</div>
  <div class="item aside">Aside</div>
  <div class="item footer">Footer</div>
</div>
css
.grid {
  display: grid;
  grid-template-columns: 200px 1fr 300px; /* 3 列 */
  grid-template-rows: auto 1fr auto; /* 3 行 */
  min-height: 100vh;
  gap: 10px;
}

.item {
  background-color: #2196f3;
  color: white;
  padding: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.header {
  grid-column: 1 / -1; /* 横跨所有列 */
  grid-row: 1; /* 第一行 */
  background-color: #333;
}

.sidebar {
  grid-column: 1; /* 第一列 */
  grid-row: 2; /* 第二行 */
  background-color: #4caf50;
}

.main {
  grid-column: 2; /* 第二列 */
  grid-row: 2; /* 第二行 */
  background-color: white;
  color: #333;
}

.aside {
  grid-column: 3; /* 第三列 */
  grid-row: 2; /* 第二行 */
  background-color: #ff9800;
}

.footer {
  grid-column: 1 / -1; /* 横跨所有列 */
  grid-row: 3; /* 第三行 */
  background-color: #333;
}

这创建了一个经典的页面布局:顶部通栏 header,中间三栏,底部通栏 footer。

使用 span 跨越多行

就像 grid-column 一样,grid-row 也可以使用 span

css
.sidebar {
  grid-column: 1;
  grid-row: 2 / span 2; /* 从第 2 行开始,跨越 2 行 */
}

实际应用:图片画廊

html
<div class="gallery">
  <div class="photo photo-1">Photo 1</div>
  <div class="photo photo-2">Photo 2</div>
  <div class="photo photo-3">Photo 3</div>
  <div class="photo photo-4">Photo 4</div>
  <div class="photo photo-5">Photo 5</div>
  <div class="photo photo-6">Photo 6</div>
</div>
css
.gallery {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 200px;
  gap: 15px;
}

.photo {
  background-size: cover;
  background-position: center;
  border-radius: 8px;
  overflow: hidden;
}

/* 创建不同大小的照片 */
.photo-1 {
  grid-column: span 2;
  grid-row: span 2;
}

.photo-3 {
  grid-column: span 2;
}

.photo-5 {
  grid-row: span 2;
}

这创建了一个动态的画廊布局,有些照片更大(占据更多空间),有些照片正常大小。

grid-area:位置简写

grid-area 是一个超级简写属性,可以同时指定行和列的起始和结束位置:

css
.item {
  grid-area: row-start / column-start / row-end / column-end;
}

例如:

css
.item {
  grid-area: 1 / 2 / 3 / 4;
  /* 相当于: */
  /* grid-row: 1 / 3; */
  /* grid-column: 2 / 4; */
  /* 从第 1 行第 2 列开始,到第 3 行第 4 列结束 */
}

虽然这很简洁,但顺序容易记错。我个人更喜欢分别使用 grid-rowgrid-column,代码更清晰。

grid-area 还有另一个用途:配合命名网格区域使用,我们会在下一节详细讲解。

对齐控制

Grid 容器可以设置所有项目的默认对齐方式,但单个项目可以覆盖这些默认值。

justify-self:单个项目的水平对齐

justify-self 控制单个项目在其单元格中的水平对齐方式,覆盖容器的 justify-items 设置。

css
.grid {
  display: grid;
  grid-template-columns: repeat(3, 200px);
  justify-items: start; /* 默认所有项目靠左 */
}

.item {
  width: 100px;
  background-color: #2196f3;
}

.item-centered {
  justify-self: center; /* 这个项目居中,其他项目靠左 */
}

.item-right {
  justify-self: end; /* 这个项目靠右,其他项目靠左 */
}

可能的值:

  • start:靠左对齐
  • end:靠右对齐
  • center:水平居中
  • stretch:拉伸填充单元格宽度

实际应用:按钮对齐

html
<div class="card">
  <h3>Premium Plan</h3>
  <p>$29/month</p>
  <ul>
    <li>Feature 1</li>
    <li>Feature 2</li>
    <li>Feature 3</li>
  </ul>
  <button class="btn-choose">Choose Plan</button>
</div>
css
.card {
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  /* 标题、价格、特性列表、按钮 */
  gap: 15px;
  padding: 30px;
  background-color: white;
  border: 1px solid #ddd;
  border-radius: 8px;
}

.btn-choose {
  justify-self: stretch; /* 按钮拉伸到整个宽度 */
  padding: 12px;
  background-color: #2196f3;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

align-self:单个项目的垂直对齐

align-self 控制单个项目在其单元格中的垂直对齐方式,覆盖容器的 align-items 设置。

css
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 150px;
  align-items: start; /* 默认所有项目靠上 */
}

.item {
  background-color: #2196f3;
  padding: 10px;
}

.item-centered {
  align-self: center; /* 这个项目垂直居中 */
}

.item-bottom {
  align-self: end; /* 这个项目靠下 */
}

.item-stretched {
  align-self: stretch; /* 这个项目拉伸到整个单元格高度 */
}

可能的值:

  • start:靠上对齐
  • end:靠下对齐
  • center:垂直居中
  • stretch:拉伸填充单元格高度

place-self:对齐简写

place-selfalign-selfjustify-self 的简写:

css
.item {
  place-self: center center;
  /* 相当于: */
  /* align-self: center; */
  /* justify-self: center; */
  /* 项目在单元格中完美居中 */
}

如果只写一个值,两个方向都使用这个值:

css
.item {
  place-self: center;
  /* 水平和垂直都居中 */
}

层叠顺序

当多个项目占据同一个单元格时,它们会重叠。默认情况下,后面的项目会覆盖前面的项目。

z-index:控制层叠顺序

你可以使用 z-index 控制重叠项目的层叠顺序:

html
<div class="grid">
  <div class="item item-1">Item 1</div>
  <div class="item item-2">Item 2</div>
  <div class="item item-3">Item 3</div>
</div>
css
.grid {
  display: grid;
  grid-template-columns: repeat(3, 150px);
  grid-auto-rows: 150px;
  gap: 10px;
}

.item {
  background-color: #2196f3;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
  border-radius: 8px;
  border: 3px solid white;
}

.item-1 {
  grid-column: 1 / 3;
  grid-row: 1 / 3;
  background-color: #f44336;
  z-index: 1; /* 在最上层 */
}

.item-2 {
  grid-column: 2 / 4;
  grid-row: 1 / 2;
  background-color: #4caf50;
  z-index: 2; /* 在中间层 */
}

.item-3 {
  grid-column: 2 / 4;
  grid-row: 2 / 3;
  background-color: #ff9800;
  z-index: 3; /* 在最顶层 */
}

在这个例子中,三个项目有重叠部分,z-index 值越大的项目显示在越上层。

实际应用:卡片堆叠效果

css
.card-stack {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  width: 300px;
  height: 200px;
}

.card {
  grid-column: 1;
  grid-row: 1;
  background-color: white;
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: transform 0.3s, z-index 0s;
}

.card:nth-child(1) {
  transform: rotate(-5deg);
  z-index: 1;
}

.card:nth-child(2) {
  transform: rotate(0deg);
  z-index: 2;
}

.card:nth-child(3) {
  transform: rotate(5deg);
  z-index: 3;
}

.card:hover {
  z-index: 10;
  transform: scale(1.05) rotate(0deg);
}

这创建了一个卡片堆叠效果,鼠标悬停时卡片会浮到最上层并放大。

实际应用示例

让我们组合使用这些属性创建一些实用的布局。

杂志式文章布局

html
<article class="magazine-article">
  <h1 class="title">Breaking: Major Discovery in Science</h1>
  <img class="hero-image" src="hero.jpg" alt="Hero" />
  <p class="intro">This is the introduction paragraph...</p>
  <p class="content">Main content goes here...</p>
  <aside class="pullquote">"This is a pull quote"</aside>
  <p class="content">More content...</p>
  <figure class="side-image">
    <img src="side.jpg" alt="Side" />
    <figcaption>Image caption</figcaption>
  </figure>
  <p class="content">Even more content...</p>
</article>
css
.magazine-article {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; /* 6 列 */
  gap: 20px;
  max-width: 1200px;
  margin: 0 auto;
  padding: 40px 20px;
}

.title {
  grid-column: 1 / -1; /* 占据所有列 */
  font-size: 48px;
  margin: 0 0 30px 0;
}

.hero-image {
  grid-column: 1 / -1; /* 占据所有列 */
  width: 100%;
  height: 400px;
  object-fit: cover;
  border-radius: 8px;
}

.intro {
  grid-column: 1 / 5; /* 占据左边 4 列 */
  font-size: 20px;
  font-weight: 500;
  color: #666;
}

.content {
  grid-column: 1 / 5; /* 主要内容占据左边 4 列 */
  line-height: 1.8;
}

.pullquote {
  grid-column: 5 / -1; /* 引用占据右边 2 列 */
  grid-row: 4 / 6; /* 跨越 2 行 */
  align-self: start;
  background-color: #f5f5f5;
  padding: 30px;
  border-left: 4px solid #2196f3;
  font-size: 24px;
  font-style: italic;
  color: #333;
}

.side-image {
  grid-column: 5 / -1; /* 边侧图片占据右边 2 列 */
  align-self: start;
  margin: 0;
}

.side-image img {
  width: 100%;
  border-radius: 8px;
}

.side-image figcaption {
  margin-top: 10px;
  font-size: 14px;
  color: #666;
  text-align: center;
}

这创建了一个专业的杂志式布局,主要内容在左边宽栏,引用和图片在右边窄栏。

产品展示网格

html
<div class="product-showcase">
  <div class="product featured">
    <h2>Featured Product</h2>
    <p>Our best seller</p>
  </div>
  <div class="product">Product 2</div>
  <div class="product">Product 3</div>
  <div class="product">Product 4</div>
  <div class="product">Product 5</div>
  <div class="product">Product 6</div>
  <div class="product">Product 7</div>
  <div class="product">Product 8</div>
</div>
css
.product-showcase {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  grid-auto-rows: 200px;
  gap: 15px;
  padding: 20px;
}

.product {
  background-color: white;
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  transition: transform 0.3s, box-shadow 0.3s;
}

.product:hover {
  transform: translateY(-5px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.product.featured {
  grid-column: span 2; /* 横跨 2 列 */
  grid-row: span 2; /* 横跨 2 行 */
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  font-size: 24px;
}

.product.featured h2 {
  margin: 0 0 10px 0;
  font-size: 32px;
}

仪表板小部件

html
<div class="dashboard">
  <div class="widget stats">
    <h3>Statistics</h3>
    <p>1,234 users</p>
  </div>
  <div class="widget chart">
    <h3>Sales Chart</h3>
    <div class="chart-area">Chart goes here</div>
  </div>
  <div class="widget activity">
    <h3>Recent Activity</h3>
    <ul>
      <li>User logged in</li>
      <li>Order placed</li>
      <li>Payment received</li>
    </ul>
  </div>
  <div class="widget notifications">
    <h3>Notifications</h3>
    <p>3 new messages</p>
  </div>
</div>
css
.dashboard {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  grid-auto-rows: minmax(150px, auto);
  gap: 20px;
  padding: 20px;
  background-color: #f5f5f5;
}

.widget {
  background-color: white;
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.widget h3 {
  margin: 0 0 15px 0;
  color: #333;
  font-size: 18px;
  border-bottom: 2px solid #2196f3;
  padding-bottom: 10px;
}

/* 让图表小部件更大 */
.chart {
  grid-column: span 2; /* 横跨 2 列 */
  grid-row: span 2; /* 横跨 2 行 */
}

.chart-area {
  height: 200px;
  background-color: #f5f5f5;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #999;
}

/* 让活动列表稍高一些 */
.activity {
  grid-row: span 2;
}

.activity ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.activity li {
  padding: 10px 0;
  border-bottom: 1px solid #eee;
}

.activity li:last-child {
  border-bottom: none;
}

常见问题和技巧

项目重叠时的处理

当项目重叠时,记住以下几点:

  1. 默认层叠顺序:后面的项目覆盖前面的项目
  2. 使用 z-index:显式控制层叠顺序
  3. 考虑透明度:使用 opacityrgba 颜色让重叠可见
  4. 添加交互:用 :hover 状态改变 z-index

响应式调整

单个项目可以在不同屏幕尺寸下占据不同的空间:

css
.featured-item {
  grid-column: span 2;
  grid-row: span 2;
}

@media (max-width: 768px) {
  .featured-item {
    grid-column: span 1; /* 在小屏幕上只占 1 列 */
    grid-row: span 1; /* 只占 1 行 */
  }
}

命名的网格线

你可以给网格线命名,让代码更有语义:

css
.grid {
  display: grid;
  grid-template-columns: [sidebar-start] 200px [sidebar-end main-start] 1fr [main-end];
}

.sidebar {
  grid-column: sidebar-start / sidebar-end;
}

.main {
  grid-column: main-start / main-end;
}

这让代码更自解释,维护更容易。

总结

Grid 项目属性给了你精确控制每个元素的能力。让我们回顾核心概念:

定位属性:

  • grid-column:控制列位置(简写:grid-column-start / grid-column-end
  • grid-row:控制行位置(简写:grid-row-start / grid-row-end
  • grid-area:同时控制行和列(或命名区域)

对齐属性:

  • justify-self:单个项目的水平对齐
  • align-self:单个项目的垂直对齐
  • place-self:对齐简写

其他:

  • z-index:控制重叠项目的层叠顺序

关键技巧:

  • 使用网格线编号精确定位
  • 使用 span 关键字指定跨越的行/列数
  • 使用负数索引从末尾开始计数
  • 混合使用自动放置和手动定位
  • -self 属性覆盖容器的默认对齐

记住:

  • 容器属性定义整体布局规则
  • 项目属性让单个元素脱颖而出
  • 组合使用两者创建复杂而灵活的布局

在下一节中,我们将学习 Grid 模板区域(Grid Template Areas),这是一种更直观、更语义化的定义布局的方法。