CSS网格和自定义形状第一部分(附代码示例)

229 阅读12分钟

在上一篇文章中,我研究了CSS Grid利用其自动放置功能创建复杂布局的能力。在另一篇文章中,我又进一步为网格布局中的图片添加了一个缩放悬停效果。这一次,我想深入研究另一种类型的网格,一种适用于形状的网格。

比如,如果图片不是完全的正方形,而是像六边形或菱形那样的形状呢?剧透一下:我们可以做到这一点。事实上,我们将结合我们已经看过的CSS网格技术,并加入一些CSSclip-pathmask 魔法,为你能想象到的任何形状的图像创建花哨的网格。

让我们从一些标记开始

我们要看的大多数布局乍一看很容易实现,但具有挑战性的部分是用同样的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网格

这有时也被称为 "蜂窝状 "网格。

CodePen嵌入回退

外面已经有很多其他的博客文章展示了如何制作这个。我在CSS-Tricks上也写过一篇。那篇文章仍然很好,而且对制作一个响应式布局有很深的研究。但在这个特定的案例中,我们将依靠一种更简单的CSS方法。

首先,让我们用 [clip-path](https://css-tricks.com/almanac/properties/c/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%)

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

CodePen嵌入回退

下一步是对图片进行翻译,以正确地将它们放在网格上:

注意,我们仍然希望其中一张图片保持在中心位置。其余的则使用CSStranslate 和良好的老式几何形状放置在其周围。下面是我为网格中的每张图片想出的模拟公式:

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) )在任何选择器中都是未定义的,因为它是在中心的那张。如果你决定使用不同的顺序,它可以是任何图片。这是我使用的顺序。

只用了几行代码,我们就得到了一个很酷的图片网格。对此,我在图片上添加了一个小的悬停效果,以使事情变得更有趣。

你猜怎么着?我们可以通过简单地更新一些数值来获得另一个六边形网格。

CodePen嵌入回退

如果你检查代码并与之前的代码进行比较,你会发现我只是简单地交换了clip-path 里面的值,我在--x--y 之间进行了切换。这就是全部!

菱形的CSS网格

菱形是一个旋转了45度的正方形的花哨字眼。

CodePen嵌入回退

同样的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));

设置完网格后,我们用CSStransforms旋转它和图片,我们就得到了这个。

CodePen 嵌入回退

请注意我是如何将它们都旋转到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%)

CodePen嵌入回退

我们就快完成了!我们需要纠正图像的大小,使它们适合在一起。否则,它们的间距很远,以至于看起来不像是一个图像的网格。

图像在绿圈的边界内,绿圈是放置图像的网格区域的内切圆。我们要做的是让图像变大,以适应红色的圆圈,也就是网格区域的包围圈。

别担心,我不会再介绍任何无聊的几何学。你只需要知道,每个圆的半径之间的关系是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%);
}

就像六边形网格一样,我们可以用那种漂亮的缩放悬停效果让事情变得更加狂热。

CodePen嵌入回退

三角形的CSS网格

CodePen 嵌入回退

你现在可能已经知道,最大的诀窍是找出clip-path ,以获得我们想要的形状。对于这个网格,每个元素都有自己的clip-path ,而前两个网格则是用一个一致的形状。所以,这一次,我们就像在处理一些不同的三角形,这些三角形组合在一起,形成一个矩形的图像网格。

顶部的三张图片

底部的三张图片

我们用下面的CSS把它们放在一个3×2的网格里。

.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%); } }

这就是我们得到的东西。

CodePen嵌入回退

最后一点是使中间一列的宽度等于0 ,以消除图像之间的空间。与我们在菱形网格中遇到的那种间距问题相同,但对于我们使用的形状,我们采用了不同的方法。

grid-template-columns: auto 0 auto;

我不得不摆弄一下clip-path ,以确保它们看起来都能像拼图一样很好地拼在一起。当中间一列的宽度为零时,原来的图片会重叠,但在对图片进行切片后,这种错觉就完美了。

CSS比萨饼网格

你猜怎么着?我们可以通过简单地在我们的网格或三角形中添加border-radiusoverflow ,得到另一个很酷的网格。 🎉

CodePen嵌入回退

CSS 拼图的网格

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

CodePen Embed Fallback

如果你没有用过maskCSS渐变,我强烈推荐我写的这篇关于这个主题的其他文章,因为它对接下来的内容会有帮助。为什么是渐变?因为这就是我们用来获得拼图形状中的圆形凹槽的方法。

现在设置网格应该是很容易的,所以让我们专注于mask

CodePen嵌入回退

如上面的演示所示,我们需要两个梯度来创建最终的形状。一个梯度创建了一个圆(绿色部分),另一个创建了右边的曲线,同时填充了顶部的部分:

--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,并更新其中的几个值来创建其他三个形状。

CodePen嵌入回退

我们有了这些形状,但没有我们需要的重叠边缘,以使它们合在一起。每张图片都被限制在它所在的网格单元内,所以这就说明了为什么现在的形状有点杂乱无章。

我们需要通过增加图像的高度/宽度来创造一个溢出。从上图来看,我们必须增加第一和第四张图片的高度,同时增加第二和第三张的宽度。你可能已经猜到了,我们需要用--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));
}

我们越来越接近了!

CodePen嵌入回退

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

CodePen嵌入回退

这里是另一个例子,我使用圆锥渐变而不是径向渐变。这给我们提供了三角形的拼图,同时保持相同的基础HTML和CSS。

CodePen 嵌入回退

最后一个例子!这次我使用clip-path ,因为它是一个我们可以动画化的属性,我们通过简单地更新控制形状的自定义属性,得到一个很酷的悬停。

CodePen 嵌入回退

收尾工作

这就是这第一部分的全部内容!通过将我们已经学到的关于CSS网格的知识与一些额外的clip-pathmask 魔法结合起来,我们能够制作具有不同类型形状的网格布局。而且我们每次都使用相同的HTML标记!而这些标记本身只不过是一个带有少量图像元素的容器而已

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

我打算把我们在这另一篇文章中一起制作的扩展图像面板的演示。

CodePen 嵌入回退

...并将其转化为人字形的图像面板这只是我们将在下一篇文章中发现的众多例子中的一个。