图解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 的元素
  • filterperspectiveclip-pathmaskmotion-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 的网格是最容易的了,只需要元素上显示设置 displaygridinline-grid,不需要显式设置grid-template-columnsgrid-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 容器上显式设置 displaygrid ,会创建一个 1 x 3 (一列三行)的网格:

.card {
    display: grid;
}

为了使这几个网格项目(.card__tag.card__thumb.card__content)层叠在一起,我们可以使用 grid-area(或单独使用 grid-columngrid-row)放置网格项目在同一个网格区域:

.card > * {
    grid-area: 1 / -1
}

在网格项目未显式设置 z-index 时,它们在 z 轴的层级会按 DOM 源顺序来决定,越出现在后面的 DOM,他越在 z 轴的顶层,离我们越近:

用 3D 来模拟他们默认情况下的立体效果,主要看 z 轴的效果:

但我们实际要的z轴层级是:

  • .card__tag 在最顶层,可以显式设置 z-index3
  • .card__content 在中间层,可以显式设置 z-index2
  • .card__thmub 在最底层,可以显式设置 z-index1

即:

.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-selfjustify-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-columnsgrid-template-rows 显式创建一个多行多列的网格:

.grid {
    display: grid;
    grid-template-columns: repeat(3, auto);
    grid-template-rows: 4fr 4fr 5fr;
}

上面代码创建了一个 3 x 3 的网格:

在网格项目上显式使用 grid-rowgrid-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-columngrid-rowz-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);
    }
}

最终你所看到的效果如下: