CSS 网格和自定义形状,第 1 部分

Avatar of Temani Afif
Temani Afif

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

在之前的文章中,我介绍了 CSS 网格利用其自动放置功能 创建复杂布局 的能力。在另一篇文章中,我更进一步,在网格布局中为图像添加了缩放悬停效果。这次,我想深入探讨另一种类型的网格,一种与形状一起工作的网格。

例如,如果图像不是完美的正方形,而是六边形或菱形形状呢?剧透警告:我们可以做到。事实上,我们将结合我们已经了解的 CSS 网格技术,并加入一些 CSS clip-pathmask 魔法,为几乎任何你能想象到的形状创建花哨的图像网格!

CSS 网格和自定义形状系列

让我们从一些标记开始

我们即将看到的许多布局乍一看可能很容易实现,但具有挑战性的是,要使用**相同的 HTML 标记**来实现它们。我们可以使用很多包装器、div 等,但这篇文章的目标是使用相同且最少的 HTML 代码,仍然获得我们想要的各种网格。毕竟,CSS 不过是一种分离样式和标记的方法。我们的样式不应该依赖于标记,反之亦然。

这样说来,让我们从这里开始

<div class="gallery">
  <img src="..." alt="...">
  <img src="..." alt="...">
  <img src="..." alt="...">
  <img src="..." alt="...">
  <!-- as many times as we want -->
</div>

这里只需要一个包含图像的容器。仅此而已!

六边形 CSS 网格

这有时也被称为“蜂窝”网格。

已经有大量其他博文展示了如何制作这个。哎呀,我在 CSS-Tricks 上 写过一篇!那篇文章仍然很好,并且深入探讨了如何制作响应式布局。但对于这个特定案例,我们将依靠更简单的 CSS 方法。

首先,让我们在图像上使用 clip-path 创建六边形形状,并将它们全部放置在同一个网格区域中,以便它们重叠。

.gallery {
  --s: 150px; /* controls the size */
  display: grid;
}

.gallery > img {
  grid-area: 1/1;
  width: var(--s);
  aspect-ratio: 1.15;
  object-fit: cover;
  clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0 50%);
}
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0 50%)

还没有花哨的东西。所有图像都是六边形,并且彼此叠加。所以看起来我们只有一个六边形图像元素,但实际上有七个。

下一步是将转换应用于图像,以将其正确放置在网格上。

请注意,我们仍然希望其中一个图像保持在中心。其余的图像使用 CSS translate 和传统的几何学放置在其周围。以下是我为网格中的每个图像想出的模拟公式

translate((height + gap)*sin(0deg), (height + gap)*cos(0))
translate((height + gap)*sin(60deg), (height + gap)*cos(60deg))
translate((height + gap)*sin(120deg), (height + gap)*cos(120deg))
translate((height + gap)*sin(180deg), (height + gap)*cos(180deg))
translate((height + gap)*sin(240deg), (height + gap)*cos(240deg))
translate((height + gap)*sin(300deg), (height + gap)*cos(300deg))

经过一些计算和优化(让我们跳过那部分无聊的部分,对吧?),我们得到了以下 CSS

.gallery {
  --s: 150px; /* control the size */
  --g: 10px;  /* control the gap */
  display: grid;
}
.gallery > img {
  grid-area: 1/1;
  width: var(--s);
  aspect-ratio: 1.15;
  object-fit: cover;
  clip-path: polygon(25% 0%, 75% 0%, 100% 50% ,75% 100%, 25% 100%, 0 50%);
  transform: translate(var(--_x,0), var(--_y,0));
}
.gallery > img:nth-child(1) { --_y: calc(-100% - var(--g)); }
.gallery > img:nth-child(7) { --_y: calc( 100% + var(--g)); }
.gallery > img:nth-child(3),
.gallery > img:nth-child(5) { --_x: calc(-75% - .87*var(--g)); }
.gallery > img:nth-child(4),
.gallery > img:nth-child(6) { --_x: calc( 75% + .87*var(--g)); }
.gallery > img:nth-child(3),
.gallery > img:nth-child(4) { --_y: calc(-50% - .5*var(--g)); }
.gallery > img:nth-child(5), 
.gallery > img:nth-child(6) { --_y: calc( 50% + .5*var(--g)); }

也许当我们获得 CSS 中的真正三角函数 时,会更容易一些!

每个图像都通过基于这些公式的 --_x--_y 变量进行平移。只有第二个图像 (nth-child(2)) 在任何选择器中都是未定义的,因为它位于中心。如果您决定使用不同的顺序,它可以是任何图像。这是我使用的顺序

只需几行代码,我们就得到了一个很酷的图像网格。为此,我在图像上添加了一个小的悬停效果,使它们看起来更花哨。

猜猜看?我们只需更新几个值,就可以获得另一个六边形网格。

如果您查看代码并将其与上一个代码进行比较,您会注意到我只是交换了 clip-path 中的值,并在 --x--y 之间切换。仅此而已!

菱形 CSS 网格

菱形对于旋转 45 度的正方形来说是一个多么花哨的词。

记住,相同的 HTML?我们首先在 CSS 中定义一个 2×2 的图像网格

.gallery {
  --s: 150px; /* controls the size */

  display: grid;
  gap: 10px;
  grid: auto-flow var(--s) / repeat(2, var(--s));
  place-items: center;
}
.gallery > img {
  width: 100%; 
  aspect-ratio: 1;
  object-fit: cover;
}

首先可能会引起您注意的是 grid 属性。它并不常用,但在它可以让你在一个声明中定义一个完整的网格方面非常有用。它不是最直观——更不用说可读——的属性,但我们在这里是为了学习发现新事物,所以让我们使用它,而不是写出所有单独的网格属性。

grid: auto-flow var(--s) / repeat(2,var(--s));

/* is equivalent to this: */
grid-template-columns: repeat(2, var(--s));
grid-auto-rows: var(--s);

这定义了两列,等于 --s 变量,并将所有行的高度也设置为 --s。由于我们有四个图像,因此我们将自动获得一个 2×2 的网格。

以下是我们可以编写它的另一种方式

grid-template-columns: repeat(2, var(--s));
grid-template-rows: repeat(2, var(--s));

……可以使用 grid 简写形式缩减

grid: repeat(2,var(--s)) / repeat(2,var(--s));

设置网格后,我们使用 CSS transform 旋转它和图像,然后得到这个

请注意,我将它们都旋转了 45deg,但方向相反。

.gallery {
  /* etc. */
  transform: rotate(45deg);
}
.gallery > img {
  /* etc. */
  transform: rotate(-45deg);
}

向负方向旋转图像可以防止它们与网格一起旋转,因此它们保持笔直。现在,我们应用一个 clip-path 从图像中裁剪出菱形形状。

clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%)

我们快完成了!我们需要修正图像的大小以使其彼此贴合。否则,它们会间隔很远,以至于看起来不像一个图像网格。

图像位于绿色圆圈的边界内,该圆圈是放置图像的网格区域的内切圆。我们想要的是使图像更大以适合红色圆圈内,红色圆圈是网格区域的外接圆。

别担心,我不会再介绍任何无聊的几何知识。您只需要知道每个圆的半径之间的关系是 2 的平方根 (sqrt(2))。这是我们需要增加图像大小以填充区域的值。我们将使用 100%*sqrt(2) = 141% 并完成!

.gallery {
  --s: 150px; /* control the size */

  display: grid;
  grid: auto-flow var(--s) / repeat(2,var(--s));
  gap: 10px;
  place-items: center;
  transform: rotate(45deg);
}
.gallery > img {
  width: 141%; /* 100%*sqrt(2) = 141% */
  aspect-ratio: 1;
  object-fit: cover;
  transform: rotate(-45deg);
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
}

与六边形网格一样,我们可以使用那个漂亮的缩放悬停效果使事情变得更花哨

三角形 CSS 网格

您可能现在已经知道,诀窍是确定 clip-path 以获得我们想要的形状。对于此网格,每个元素都有其自己的 clip-path 值,而最后两个网格使用一致的形状。因此,这一次,就像我们正在处理一些不同的三角形形状,它们组合在一起形成一个矩形的图像网格。

顶部的三个图像
底部的三个图像

我们将它们放在一个 3×2 的网格中,并使用以下 CSS

.gallery {
  display: grid;
  gap: 10px; 
  grid-template-columns: auto auto auto; /* 3 columns */
  place-items: center;
}
.gallery > img {
  width: 200px; /* controls the size */
  aspect-ratio: 1;
  object-fit: cover;
}
/* the clip-path values */
.gallery > img:nth-child(1) { clip-path: polygon(0 0, 50% 0, 100% 100% ,0 100%); }
.gallery > img:nth-child(2) { clip-path: polygon(0 0, 100% 0, 50% 100%); }
.gallery > img:nth-child(3) { clip-path: polygon(50% 0, 100% 0, 100% 100%, 0 100%); }
.gallery > img:nth-child(4) { clip-path: polygon(0 0, 100% 0, 50% 100%, 0 100%); }
.gallery > img:nth-child(5) { clip-path: polygon(50% 0, 100% 100%, 0% 100%); }
.gallery > img:nth-child(6) { clip-path: polygon(0 0, 100% 0 ,100% 100%, 50% 100%); } }

这是我们得到的结果

最后的润色是将中间列的宽度设置为 0,以消除图像之间的间隙。与菱形网格遇到的相同类型的间距问题,但我们使用的形状的方法不同

grid-template-columns: auto 0 auto;

我不得不调整 clip-path 值,以确保它们看起来像拼图一样完美地组合在一起。当中间列宽度为零时,原始图像会重叠,但切片图像后,错觉是完美的

CSS 比萨饼网格

猜猜看?我们只需在网格或三角形形状中添加 border-radiusoverflow,就可以获得另一个很酷的网格。🎉

拼图碎片 CSS 网格

这次我们将使用 CSS mask 属性使图像看起来像拼图碎片。

如果你之前没有在CSS渐变中使用过mask,我强烈建议你阅读我之前写的一篇关于这个主题的文章,因为它会帮助你理解接下来的内容。为什么使用渐变?因为我们正是利用它来创建拼图形状中的圆形缺口。

现在设置网格应该轻而易举了,所以让我们把注意力放在mask部分。

如上图所示,我们需要两个渐变来创建最终的形状。一个渐变创建一个圆形(绿色部分),另一个渐变创建右侧曲线并填充顶部部分。

--g: 6px; /* controls the gap */
--r: 42px;  /* control the circular shapes */

background: 
  radial-gradient(var(--r) at left 50% bottom var(--r), green 95%, #0000),
  radial-gradient(calc(var(--r) + var(--g)) at calc(100% + var(--g)) 50%, #0000 95%, red)
  top/100% calc(100% - var(--r)) no-repeat;

两个变量控制着形状。--g变量仅仅是网格间隙。我们需要考虑间隙,以便正确放置我们的圆形,以便在整个拼图组合在一起时完美重叠。--r变量控制拼图形状的圆形部分的大小。

现在我们使用相同的CSS,并更新其中的几个值来创建其他三种形状。

我们有了形状,但还没有使它们组合在一起所需的重叠边缘。每个图像都限制在其所在的网格单元格中,所以目前形状有点杂乱无章也就不足为奇了。

我们需要通过增加图像的高度/宽度来创建溢出效果。从上图可以看出,我们必须增加第一个和第四个图像的高度,而增加第二个和第三个图像的宽度。你可能已经猜到了,我们需要使用--r变量来增加它们。

.gallery > img:is(:nth-child(1),:nth-child(4)) {
  width: 100%;
  height: calc(100% + var(--r));
}
.gallery > img:is(:nth-child(2),:nth-child(3)) {
  height: 100%;
  width: calc(100% + var(--r));
}

我们越来越接近了!

我们创建了重叠,但默认情况下,我们的图像要么向右重叠(如果我们增加宽度),要么到底部重叠(如果我们增加高度)。但对于第二个和第四个图像,这不是我们想要的。解决方法是在这两个图像上使用place-self: end,我们的完整代码变成这样。

这是一个其他的例子,我使用的是锥形渐变而不是径向渐变。这让我们可以在保持相同的底层HTML和CSS的情况下,创建三角形的拼图碎片。

最后一个!这次我使用的是clip-path,由于它是一个我们可以进行动画化的属性,所以我们只需更新控制形状的自定义属性,就可以获得一个很酷的悬停效果。

总结

第一部分就到这里!通过将我们之前学到的关于CSS Grid的知识与一些额外的clip-pathmask魔法结合起来,我们能够创建具有各种形状的网格布局。而且我们每次都使用相同的HTML标记!并且标记本身只不过是一个包含少量图像元素的容器!

在第二部分中,我们将探索更复杂的外观的网格,以及更花哨的形状和悬停效果。

我计划采用我们在这篇文章中一起制作的扩展图像面板演示。

…并将其转换为锯齿形图像面板!这仅仅是我们将在下一篇文章中发现的众多示例中的一个。