图解CSS:Grid布局案例之构建重叠布局
如果一直是跟着 CSS Grid 布局这个系列到这里,你应该还记得,在 CSS Grid 布局中,我们可以通过网格项目放置的方式,让不同的元素重叠在一起,并且通过 CSS 的 z-index
来控制网格项目在 z
轴上的层叠顺序。也就是说,以往需要使用 CSS 的 position
的绝对定位(absolute
)来实现的布局,现在可以直接使用 CSS Grid 来解决。在这一节,我们主要来看如何使用 CSS Grid 实现元素叠加的布局效果。
CSS中的层叠
正如上图所示,在 Web 中类似于上图这种元素叠加在一起的布局很常见。他们有着相同的共性:元素一层一层叠加在一起,有完全叠加,也有部分叠加:
在 CSS 的世界中,他们都是 z
轴的叠加,而且控制他们的层级需要依赖于 z-index
来控制:
而且 z
轴的层级叠又是一个复杂的体系:
即使抛开技术体系,在众多 Web 开发者世界中,甚至包括很多 CSSer,都认为要触发 z-index
生效,必须是 position
为非 static
。事实上,文档中的层叠上下文由满足以下任意一个条件的元素形成:
- 根元素 (HTML)
z-index
值不为auto
的 绝对/相对定位position
值为非static
- 一个
z-index
值不为auto
的 Flex 项目 或 Grid 项目 opacity
属性值小于1
的元素transform
属性值不为none
的元素mix-blend-mode
属性值不为normal
的元素filter
、perspective
、clip-path
、mask
、motion-path
值不为none
的元素isolation
属性被设置为isolate
的元素- 在
will-change
中指定了任意 CSS 属性,即便你没有直接指定这些属性的值 -webkit-overflow-scrolling
属性被设置touch
的元素
正如列表中第三条所说:
一个
z-index
值不为auto
的 Flex 项目 或 Grid 项目!
言外之意,我们经常使用 position: absolute
来实现元素叠加的布局效果。有了 CSS Grid 之后,就不再需要这样做了。至少在 网格项目上不再需要显示设置position
值为非static
。
先从简单地卡片叠加开始
我们先从最简单,最常见的示例开始,那就是卡片叠加,也就是《处理图片上文字效果的几种姿势》文中所介绍的 UI 效果:
正如上图所示,每一张卡片对应的就是一个网格容器,而且是一个 1 x 1
的网格。根据前面所学,创建一个 1 x 1
的网格是最容易的了,只需要元素上显示设置 display
为 grid
或 inline-grid
,不需要显式设置grid-template-columns
和 grid-template-rows
就可以得到。
把单独一张卡片拿出来举例,它的z
轴层级顺序看上去像下图:
如果用代码来实现的话,它的 HTML 结构大致像下面这样:
<!-- HTML -->
<div class="card">
<div class="card__tag">Must Try</div>
<div class="card__content">
<h2>Card Title</h2>
<p>Some des will go here and I need it to wrap into lines</p>
</div>
<div class="card__thumb">
<img src="https://picsum.photos/966/358?random=1" width="966" height="358" alt="" />
</div>
</div>
当然,你也可以根据自己的喜好,调整上面的结构、元素类型以及类名。在上面这个 HTML 结构上,如果在 .card
容器上显式设置 display
为 grid
,会创建一个 1 x 3
(一列三行)的网格:
.card {
display: grid;
}
为了使这几个网格项目(.card__tag
、.card__thumb
和 .card__content
)层叠在一起,我们可以使用 grid-area
(或单独使用 grid-column
和grid-row
)放置网格项目在同一个网格区域:
.card > * {
grid-area: 1 / -1
}
在网格项目未显式设置 z-index
时,它们在 z
轴的层级会按 DOM 源顺序来决定,越出现在后面的 DOM,他越在 z
轴的顶层,离我们越近:
用 3D 来模拟他们默认情况下的立体效果,主要看 z
轴的效果:
但我们实际要的z
轴层级是:
.card__tag
在最顶层,可以显式设置z-index
为3
.card__content
在中间层,可以显式设置z-index
为2
.card__thmub
在最底层,可以显式设置z-index
为1
即:
.card__tag {
z-index: 3;
}
.card__content {
z-index: 2;
}
.card__thumb {
z-index: 1;
}
添加一些其他样式,现在三个网格项目的层级(z-index
)不同了:
但最终期望的效果是:
.card__tag
并不希望平铺在整个卡片上.card__content
文本内容居于卡片底部
实现这个效果,只需要稍微添加一点 CSS 即可达到。在.card_tag
上使用 align-self
和 justify-self
让其在左上角:
.card__tag {
align-self: start;
justify-self: start;
margin-top: 2rem;
}
在 .card__content
使用 Flexbox 布局,并且让其在侧轴底部对齐:
.card__content {
display: flex;
flex-direction: column;
justify-content: flex-end;
}
最终效果如下:
你还可以使用同样的方式,来实现像下图这样的横幅效果:
CSS Grid 还可以结合 display: contens
在不改变上面 HTML 的结构状态下,轻易实现像下图这几种卡片布局效果:
拿第二张卡片为例:
.card:nth-child(2) h2 {
grid-row: 1;
}
.card:nth-child(2) p {
grid-row: 2;
}
.card:nth-child(2) .card__thumb {
grid-row: 3;
margin-bottom: -15px;
}
.card:nth-child(2) .card__tag {
grid-row: 3;
justify-self: end;
align-self: end;
margin-bottom: 1rem;
border-radius: 10rem 0 0 10rem;
padding: 0.75rem 1rem 0.75rem 1.75rem;
margin-right: -15px;
}
.card:nth-child(2) img {
border-radius: 0 0 5px 5px;
}
详细代码如下面示例所示:
把示例中的每张卡片的网格线编号显示出来,像下图这样:
网格项目部分叠加
上示卡片示例是一个非常简单的示例,他的特点就是完全叠加。接下来,我们来看网格项目部分叠加,比如 @Bart Veneman 和 @Jen Simmons 提供的示例:
其实这种效果也很简单,从上面的网格示意图中,不难发现,不同网格项目有部分区域处理同一个网格区域(或单元格)。比如我们一个这样的简单的结构:
<div class="grid">
<div class="item"></div>
<div class="item"></div>
</div>
在 .grid
容器上使用 grid-template-columns
和 grid-template-rows
显式创建一个多行多列的网格:
.grid {
display: grid;
grid-template-columns: repeat(3, auto);
grid-template-rows: 4fr 4fr 5fr;
}
上面代码创建了一个 3 x 3
的网格:
在网格项目上显式使用 grid-row
和 grid-column
放置其在网格容器中的位置:
.item:nth-child(1) {
grid-row: 2 / 4;
grid-column: 1 / 3;
background: rebeccapurple;
}
.item:nth-child(2) {
grid-row: 1 / 4;
grid-column: 2 / 4;
background: rgb(23 123 23 / .5);
}
你会发现,网格项目一和网格项目有部分重叠在一起,比如下图中红框框起来的部分就是重叠部分:
基于这个原理,我们可以把示意内容换成想要的内容。
<!-- HTML -->
<section>
<h1>title</h1>
<div class="content">
<p>lorem</p>
<a href="#">learn more</a>
</div>
<div class="thumb">
<img src="" alt="" />
</div>
</section>
有关于布局的关键CSS:
section {
display: grid;
grid-template-rows: repeat(3, auto);
grid-template-columns: 4fr 4fr 5fr;
grid-template-areas:
". . title"
"thumb thumb content"
"thumb thumb .";
max-width: 50vw;
}
section::after {
content: "";
grid-column: 2/-1;
grid-row: 1/3;
z-index: -1;
background: #fff;
box-shadow: 0 0.2em 1em rgb(61 48 41 / 20%);
}
@media (max-width: 60em) {
section {
grid-template-columns: auto;
grid-template-rows: auto;
grid-template-areas:
"title"
"content"
"thumb";
}
section::after {
grid-column: 1 / 2;
grid-row: 1 / 4;
}
}
.thumb {
grid-area: thumb;
}
h1 {
grid-area: title;
}
.content {
grid-area: content;
}
最终效果如下:
这个效果还是一个带有响应式的效果:
@Jen Simmons写的示例效果如下:
使用嵌套网格构建叠加布局
接下来看一个稍微复杂一点点的叠加布局,就是网格嵌套实现的叠加效果。比如 @Morten Rand-Hendriksen 的这个示例:
打开网格审查器,显示出网格线:
示例在窄屏时两个网格完全重叠在一起,在宽屏时两个网格部分重叠,而且这两个网格是相互嵌套的。我们来看示例的关键代码:
<!-- HTML -->
<div class="card"><!-- 父网格 -->
<img src="">
<figcaption><!-- 子网格,不是subgrid 是 grid -->
<blockquote></blockquote>
<cite></cite>
<p class="credit"></p>
</figcaption>
</div>
显示创建父网格 .card
,是一个五行六列的网格:
.card {
display: grid;
grid-template-columns:
var(--indent)
calc(3 * var(--indent))
1fr
1fr
calc(2 * var(--indent))
var(--indent);
grid-template-rows:
var(--indent)
calc(3 * var(--indent))
1fr calc(2 * var(--indent))
var(--indent);
}
另外在 figcaption
显式创建了一个两行三列的网格:
figcaption {
display: grid;
grid-template-columns: 3fr 1fr 2fr;
}
相应的网格项目使用 grid-column
、grid-row
和 z-index
来放置网格项目位置和层叠顺序,其中布局的关键代码如下:
.card {
display: grid;
grid-template-columns:
var(--indent) calc(3 * var(--indent)) 1fr 1fr calc(2 * var(--indent))
var(--indent);
grid-template-rows:
var(--indent) calc(3 * var(--indent)) 1fr calc(2 * var(--indent))
var(--indent);
}
.card::before {
grid-column: 2/-1;
grid-row: 2/-1;
}
.card::after {
grid-column: 1/-2;
grid-row: 1/-2;
z-index: -1;
}
.card img {
grid-column: 4/5;
grid-row: 3/4;
}
.card figcaption {
grid-column: 3/5;
grid-row: 3/4;
display: grid;
grid-template-columns: 3fr 1fr 2fr;
}
.card blockquote {
grid-column: 1/3;
align-self: flex-end;
}
.card cite {
grid-column: 1/2;
}
.card .credit {
grid-column: 2/4;
place-self: flex-end;
}
这个示例也是一个响应式布局,但我们只是在媒体查询中改变了自定义属性 --indent
的值:
:root {
--indent: 0;
}
@media screen and (min-width: 40rem) {
:root {
--indent: clamp(1.5rem, 4vw, 2.5rem);
}
}
最终你所看到的效果如下: