在之前的文章中,我介绍了 CSS 网格利用其自动放置功能 创建复杂布局 的能力。在另一篇文章中,我更进一步,在网格布局中为图像添加了缩放悬停效果。这次,我想深入探讨另一种类型的网格,一种与形状一起工作的网格。
例如,如果图像不是完美的正方形,而是六边形或菱形形状呢?剧透警告:我们可以做到。事实上,我们将结合我们已经了解的 CSS 网格技术,并加入一些 CSS clip-path
和 mask
魔法,为几乎任何你能想象到的形状创建花哨的图像网格!
让我们从一些标记开始
我们即将看到的许多布局乍一看可能很容易实现,但具有挑战性的是,要使用**相同的 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%);
}
还没有花哨的东西。所有图像都是六边形,并且彼此叠加。所以看起来我们只有一个六边形图像元素,但实际上有七个。
下一步是将转换应用于图像,以将其正确放置在网格上。
请注意,我们仍然希望其中一个图像保持在中心。其余的图像使用 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
从图像中裁剪出菱形形状。
我们快完成了!我们需要修正图像的大小以使其彼此贴合。否则,它们会间隔很远,以至于看起来不像一个图像网格。
图像位于绿色圆圈的边界内,该圆圈是放置图像的网格区域的内切圆。我们想要的是使图像更大以适合红色圆圈内,红色圆圈是网格区域的外接圆。
别担心,我不会再介绍任何无聊的几何知识。您只需要知道每个圆的半径之间的关系是 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-radius
和 overflow
,就可以获得另一个很酷的网格。🎉
拼图碎片 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-path
和mask
魔法结合起来,我们能够创建具有各种形状的网格布局。而且我们每次都使用相同的HTML标记!并且标记本身只不过是一个包含少量图像元素的容器!
在第二部分中,我们将探索更复杂的外观的网格,以及更花哨的形状和悬停效果。
我计划采用我们在这篇文章中一起制作的扩展图像面板演示。
…并将其转换为锯齿形图像面板!这仅仅是我们将在下一篇文章中发现的众多示例中的一个。
这太酷了!我对您涉及自定义属性和数学技巧的教程总是印象深刻。
我认为如果您在这些类型的帖子中更多地深入探讨数学或包含图表,将会有所帮助。我不得不把蜂窝状图案画在纸上,才能理解数学原理,尽管事后看来,它只是基本的几何知识。
对于其他想知道的人:蜂窝圆的半径为
(size + gap)
,我们沿着它的圆周放置六个图像,所以它们之间的间隔为360/60 = 60
度。然后只需形成直角三角形并进行计算,在需要的地方插入sin(a)或cos(a)。我真的很希望CSS有三角函数。那样我们就可以为每个图像分配角度值。这样,我们只需要两个calc函数:一个用于x,一个用于y。随着图像角度的变化,x/y坐标也会发生变化!
我总是试图在数学内容和非数学内容之间取得平衡。我确实有一些文章深入探讨了数学公式的细节,但并非一直如此,以免文章变得枯燥乏味,尤其不是每个人都愿意花大量时间学习数学。
既然你喜欢数学部分,这里还有另一个实现,你不需要使用sin()/cos(),但这次你可以像你想要的那样指定角度;)
我让你去深入研究代码背后的逻辑 :)
感谢分享。
这些演示很不错。
你会如何处理花朵形状,比如这张图片中的形状?尤其是在叶子数量不同的情况下?
叶子形状可以用CSS实现吗,还是最好使用SVG解决方案?
感谢任何提示。
太棒了
一如既往,精彩的文章……
不错的演示
Windows上的Chrome在缩放动画方面表现不佳,并且我在图像边缘出现随机的出血现象,类似于此SO帖子中的示例:https://stackoverflow.com/questions/67736210/svg-mask-is-bleeding-on-canvas-edges
这与它移动时的抗锯齿有关。可能可以使用父元素来覆盖边缘进行hacky修复,或者可能只是等待Chrome修复!
您是大师。谢谢。我期待着最后一个没有解释的示例。
如果能与
shape-outside
https://mdn.org.cn/en-US/docs/Web/CSS/shape-outside一起使用会非常酷,但我认为这只是针对文本的,对吧?与Graeme一样,我在Firefox Linux上的动画图像上看到了边缘线(甚至在它们动画之前),并且看起来Chrome移动端也存在这个问题。
https://stackoverflow.com/questions/51542919/clip-path-on-chrome-leaves-a-strange-line-on-the-edge
但这并没有削弱文章的大部分内容,文章很棒。现在开始深入研究第二部分。
我非常喜欢你的教程!!!我学到了很多东西,并将密切关注下一个教程。
非常感谢你考虑到我们这些初学者,我们并不总是很喜欢数学。
真诚的问候!!!
Daniel
伙计,你玩得太开心了!哇!
我从未见过这种类型的代码,而且有趣的是,我当时正在思考数学和代码,而你却用菱形来展示图像!什么!?是的,菱形。非常酷的东西。我需要花一点时间来理解这些东西,但伙计,你做这些事情的时候玩得太开心了。不错。