好的,所以我们上次查看时,我们正在使用 CSS Grid 并将其与 CSS clip-path
和 mask
技术结合使用来创建具有奇特形状的网格。
以下是我们一起制作的众多出色网格之一
准备好进行第二轮了吗?我们仍在使用 CSS Grid、clip-path
和 mask
,但在本文结束时,我们将获得不同的方式来排列网格上的图像,包括一些炫酷的悬停效果,这些效果可以为查看图片提供真实的交互式体验。
猜猜看?我们使用上次使用的相同标记。以下是再次显示的标记
<div class="gallery">
<img src="..." alt="...">
<img src="..." alt="...">
<img src="..." alt="...">
<img src="..." alt="...">
<!-- as many times as we want -->
</div>
与上一篇文章一样,我们只需要一个包含图像的容器。仅此而已!
嵌套图像网格
上一次,我们的网格是,嗯,典型的图像网格。除了我们用其遮罩的整洁形状之外,就我们如何定位内部图像而言,它们是相当标准的对称网格。
让我们尝试将一个图像嵌套在网格的中心
我们首先为四张图像设置一个 2✕2 网格
.gallery {
--s: 200px; /* controls the image size */
--g: 10px; /* controls the gap between images */
display: grid;
gap: var(--g);
grid-template-columns: repeat(2, auto);
}
.gallery > img {
width: var(--s);
aspect-ratio: 1;
object-fit: cover;
}
目前还没有复杂的东西。下一步是剪切图像的角以创建嵌套图像的空间。我已经有一篇关于如何使用clip-path
和mask
剪切角的详细文章。您还可以使用我的在线生成器获取遮罩角的 CSS。
我们这里需要以等于90deg
的角度切出角。我们可以使用同一篇文章中的相同圆锥渐变技术来做到这一点
.gallery > img {
mask: conic-gradient(from var(--_a), #0000 90deg, #000 0);
}
.gallery > img:nth-child(1) { --_a: 90deg; }
.gallery > img:nth-child(2) { --_a: 180deg; }
.gallery > img:nth-child(3) { --_a: 0deg; }
.gallery > img:nth-child(4) { --_a:-90deg; }
我们可以使用同一篇文章中用于剪切角的clip-path
方法,但使用渐变进行遮罩在这里更合适,因为我们对所有图像都具有相同的配置——我们只需要旋转(使用变量--_a
定义)即可获得效果,因此我们从内部而不是外部边缘进行遮罩。

现在,我们可以将嵌套图像放置在遮罩空间内。首先,确保我们在 HTML 中有一个第五个图像元素
<div class="gallery">
<img src="..." alt="...">
<img src="..." alt="...">
<img src="..." alt="...">
<img src="..." alt="...">
<img src="..." alt="...">
</div>
我们将依靠良好的旧绝对定位将其放置在那里
.gallery > img:nth-child(5) {
position: absolute;
inset: calc(50% - .5*var(--s));
clip-path: inset(calc(var(--g) / 4));
}
该inset
属性允许我们使用单个声明将图像放置在中心。我们知道图像的大小(使用变量--s
定义),并且我们知道容器的大小等于 100%。我们进行一些计算,并且距每个边缘的距离应等于(100% - var(--s))/2
。

您可能想知道为什么我们在这里完全使用clip-path
。我们将其与嵌套图像一起使用以获得一致的间隙。如果我们要删除它,您会注意到所有图像之间的间隙不一致。这样,我们就可以从第五个图像中剪切一点,以获得其周围的正确间距。
完整的代码再次显示
.gallery {
--s: 200px; /* controls the image size */
--g: 10px; /* controls the gap between images */
display: grid;
gap: var(--g);
grid-template-columns: repeat(2, auto);
position: relative;
}
.gallery > img {
width: var(--s);
aspect-ratio: 1;
object-fit: cover;
mask: conic-gradient(from var(--_a), #0000 90deg, #000 0);
}
.gallery > img:nth-child(1) {--_a: 90deg}
.gallery > img:nth-child(2) {--_a:180deg}
.gallery > img:nth-child(3) {--_a: 0deg}
.gallery > img:nth-child(4) {--_a:-90deg}
.gallery > img:nth-child(5) {
position: absolute;
inset: calc(50% - .5*var(--s));
clip-path: inset(calc(var(--g) / 4));
}
现在,你们中的许多人可能也会想知道:当我们可以将最后一个图像放在顶部并为其添加边框时,为什么要做所有这些复杂的事情?这样可以隐藏嵌套图像下方的图像而无需遮罩,对吧?
那是真的,我们将得到以下结果
没有mask
,没有clip-path
。是的,代码易于理解,但有一个小缺点:边框颜色需要与主背景相同才能使错觉完美。这个小缺点足以让我使代码更复杂,以换取独立于背景的真正透明度。我并不是说边框方法不好或错误。我建议在大多数背景已知的情况下使用它。但我们在这里是为了探索新事物,最重要的是,构建不依赖于其环境的组件。
这次让我们尝试另一种形状
这次,我们使嵌套图像成为圆形而不是正方形。这对于使用border-radius
来说是一项简单的任务,但我们需要为其他图像使用圆形切口。但是,这次我们将依靠radial-gradient()
而不是conic-gradient()
来获得这种漂亮的圆形外观。
.gallery > img {
mask:
radial-gradient(farthest-side at var(--_a),
#0000 calc(50% + var(--g)/2), #000 calc(51% + var(--g)/2));
}
.gallery > img:nth-child(1) { --_a: calc(100% + var(--g)/2) calc(100% + var(--g)/2); }
.gallery > img:nth-child(2) { --_a: calc(0% - var(--g)/2) calc(100% + var(--g)/2); }
.gallery > img:nth-child(3) { --_a: calc(100% + var(--g)/2) calc(0% - var(--g)/2); }
.gallery > img:nth-child(4) { --_a: calc(0% - var(--g)/2) calc(0% - var(--g)/2); }
所有图像都使用与上一个示例相同的配置,但我们每次都会更新中心点。

上图说明了每个圆的中心点。但是,在实际代码中,您会注意到我也考虑了间隙,以确保所有点都在相同位置(网格的中心),以便如果我们将它们组合在一起,则可以获得连续的圆。
现在我们有了布局,让我们谈谈悬停效果。如果您没有注意到,一个很酷的悬停效果会增加嵌套图像的大小并相应地调整其他所有内容。增加大小是一项相对简单的任务,但更新渐变则更为复杂,因为默认情况下无法为渐变设置动画。为了克服这一点,我将使用font-size
技巧来实现渐变动画。
如果检查渐变代码,您可以看到我添加了1em
mask:
radial-gradient(farthest-side at var(--_a),
#0000 calc(50% + var(--g)/2 + 1em), #000 calc(51% + var(--g)/2 + 1em));
众所周知,em
单位相对于父元素的font-size
,因此更改.gallery
的font-size
也会更改计算出的em
值——这就是我们正在使用的技巧。我们正在将font-size
从值0
动画到给定值,结果,渐变也进行了动画,使切口部分更大,跟随变大的嵌套图像的大小。
以下是突出显示悬停效果中涉及的部分的代码
.gallery {
--s: 200px; /* controls the image size */
--g: 10px; /* controls the gaps between images */
font-size: 0; /* initially we have 1em = 0 */
transition: .5s;
}
/* we increase the cut-out by 1em */
.gallery > img {
mask:
radial-gradient(farthest-side at var(--_a),
#0000 calc(50% + var(--g)/2 + 1em), #000 calc(51% + var(--g)/2 + 1em));
}
/* we increase the size by 2em */
.gallery > img:nth-child(5) {
width: calc(var(--s) + 2em);
}
/* on hover 1em = S/5 */
.gallery:hover {
font-size: calc(var(--s) / 5);
}
如果我们想要为渐变或其他无法设置动画的属性设置动画,则font-size
技巧非常有用。使用@property定义的自定义属性可以解决此类问题,但对它的支持在撰写本文时仍然不足。
在尝试解决Twitter 上的一个挑战时,我从@SelenIT2那里发现了font-size
技巧。
另一个形状?走起!
这次,我们将嵌套图像剪切成菱形。我将让您作为练习来剖析代码,以弄清楚我们是如何到达这里的。您会注意到,结构与我们的示例相同。唯一的区别在于我们如何使用渐变来创建形状。深入研究并学习!
圆形图像网格
我们可以将我们在这里和之前文章中学到的知识结合起来,以制作更令人兴奋的图像网格。这次,让我们使网格中的所有图像都呈圆形,并且在悬停时,展开一个图像以显示整个图像,因为它覆盖了其余的照片。
网格的 HTML 和 CSS 结构与之前没有什么不同,因此让我们跳过这一部分,而是专注于我们想要的圆形和悬停效果。
我们将使用clip-path
及其circle()
函数来——您猜对了!——从图像中剪切一个圆。

该图说明了第一个图像使用的clip-path
。左侧显示图像的初始状态,右侧显示悬停状态。您可以使用此在线工具来播放和可视化clip-path
值。
对于其他图像,我们可以更新圆的中心(70% 70%
)以获取以下代码
.gallery > img:hover {
--_c: 50%; /* same as "50% at 50% 50%" */
}
.gallery > img:nth-child(1) {
clip-path: circle(var(--_c, 55% at 70% 70%));
}
.gallery > img:nth-child(2) {
clip-path: circle(var(--_c, 55% at 30% 70%));
}
.gallery > img:nth-child(3) {
clip-path: circle(var(--_c, 55% at 70% 30%));
}
.gallery > img:nth-child(4) {
clip-path: circle(var(--_c, 55% at 30% 30%));
}
请注意,我们如何在var()
内将clip-path
值定义为回退。这样可以让我们通过设置--_c
变量的值来更轻松地在悬停时更新值。当使用circle()
时,中心点的默认位置是50% 50%
,因此我们可以省略它以获得更简洁的代码。这就是为什么您看到我们只设置50%
而不是50% at 50% 50%
。
然后,我们在悬停时将图像的大小增加到网格的整体大小,以便我们可以覆盖其他图像。我们还确保悬停图像上的z-index
具有更高的值,因此它是我们堆叠上下文中的顶部图像。
.gallery {
--s: 200px; /* controls the image size */
--g: 8px; /* controls the gap between images */
display: grid;
grid: auto-flow var(--s) / repeat(2, var(--s));
gap: var(--g);
}
.gallery > img {
width: 100%;
aspect-ratio: 1;
cursor: pointer;
z-index: 0;
transition: .25s, z-index 0s .25s;
}
.gallery > img:hover {
--_c: 50%; /* change the center point on hover */
width: calc(200% + var(--g));
z-index: 1;
transition: .4s, z-index 0s;
}
.gallery > img:nth-child(1){
clip-path: circle(var(--_c, 55% at 70% 70%));
place-self: start;
}
.gallery > img:nth-child(2){
clip-path: circle(var(--_c, 55% at 30% 70%));
place-self: start end;
}
.gallery > img:nth-child(3){
clip-path: circle(var(--_c, 55% at 70% 30%));
place-self: end start;
}
.gallery > img:nth-child(4){
clip-path: circle(var(--_c, 55% at 30% 30%));
place-self: end;
}
place-self
属性是怎么回事?为什么我们需要它以及为什么每个图像都有一个特定的值?
您还记得我们在上一篇文章中创建拼图块网格时遇到的问题吗?我们增加了图像的大小以创建溢出,但一些图像的溢出不正确。我们使用place-self
属性修复了它们。
这里也是同样的问题。我们正在增加图像的大小,以便每个图像都溢出其网格单元格。但是,如果我们不做任何操作,所有图像都将在网格的右侧和底部溢出。我们需要的是
- 第一个图像溢出右下边缘(默认行为),
- 第二个图像溢出左下边缘,
- 第三个图像溢出右上边缘,以及
- 第四个图像溢出左上边缘。
为了实现这一点,我们需要使用place-self
属性正确放置每个图像。

如果您不熟悉place-self
,它是justify-self
和align-self
的简写,用于水平和垂直放置元素。当它采用一个值时,两种对齐方式都使用该值。
展开式图像面板
在之前的一篇文章中,我创建了一个很酷的缩放效果,它应用于图像网格,我们可以控制所有内容:行数、列数、大小、缩放因子等。
一个特定的案例是经典的展开面板,我们只有一行和一个全宽容器。
我们将以此为例,并将其与形状结合起来!
在继续之前,我强烈建议您阅读我的另一篇文章,以了解我们将要介绍的技巧是如何工作的。查看那篇文章,然后我们将在这里继续专注于创建面板形状。
首先,让我们从简化代码并删除一些变量开始。
我们只需要一行,并且列数应该根据图像的数量进行调整。这意味着我们不再需要行数 (--n
) 和列数 (--m
) 的变量,但我们需要使用 grid-auto-flow: column
,允许网格在添加新图像时自动生成列。我们将为我们的容器考虑一个固定高度;默认情况下,它将是全宽。
让我们将图像剪裁成倾斜的形状。

clip-path: polygon(S 0%, 100% 0%, (100% - S) 100%, 0% 100%);
再次,每个图像都包含在其网格单元格中,因此图像之间的空间比我们想要的要多。

我们需要增加图像的宽度以创建重叠。我们将 min-width:
100%
替换为 min-width: calc(100% + var(--s))
,其中 --s
是一个控制形状的新变量。
现在我们需要修复第一个和最后一个图像,以便它们以某种方式从页面上溢出而没有间隙。换句话说,我们可以从第一个图像的左侧和最后一个图像的右侧移除倾斜。我们需要一个新的 clip-path
特别用于这两个图像。
我们还需要修正溢出。默认情况下,所有图像都将在两侧溢出,但对于第一个图像,我们需要右侧溢出,而对于最后一个图像,我们需要左侧溢出。
.gallery > img:first-child {
min-width: calc(100% + var(--s)/2);
place-self: start;
clip-path: polygon(0 0,100% 0,calc(100% - var(--s)) 100%,0 100%);
}
.gallery > img:last-child {
min-width: calc(100% + var(--s)/2);
place-self: end;
clip-path: polygon(var(--s) 0,100% 0,100% 100%,0 100%);
}
最终结果是一个漂亮的倾斜图像展开面板!
我们可以添加任意数量的图像,网格将自动调整。此外,我们只需要控制一个值来控制形状!
由于我们正在处理单行元素,因此我们可以使用 Flexbox 创建相同的布局。这是我的实现。
当然,倾斜的图像很酷,但锯齿形图案怎么样?我已经在上一篇文章的结尾中对此进行了预告。
我在这里所做的只是用 mask
替换 clip-path
……你猜怎么着?我已经有一篇关于创建这种锯齿形形状的详细文章了——更不用说一个在线生成器来获取代码。看到所有东西是如何组合在一起的吗?
这里最棘手的部分是确保锯齿完美对齐,为此,我们需要为每个 :nth-child(odd)
图像元素添加一个偏移量。
.gallery > img {
mask:
conic-gradient(from -135deg at right, #0000, #000 1deg 89deg, #0000 90deg)
100% calc(50% + var(--_p, 0%))/51% calc(2*var(--s)) repeat-y,
conic-gradient(from 45deg at left, #0000, #000 1deg 89deg, #0000 90deg)
0% calc(50% + var(--_p, 0%))/51% calc(2*var(--s)) repeat-y;
}
/* we add an offset to the odd elements */
.gallery > img:nth-child(odd) {
--_p: var(--s);
}
.gallery > img:first-child {
mask:
conic-gradient(from -135deg at right, #0000, #000 1deg 89deg, #0000 90deg)
0 calc(50% + var(--_p, 0%))/100% calc(2*var(--s));
}
.gallery > img:last-child {
mask:
conic-gradient(from 45deg at left, #0000, #000 1deg 89deg, #0000 90deg)
0 calc(50% + var(--_p, 0%)) /100% calc(2*var(--s));
}
注意 --_p
变量的使用,它将回退到 0%
,但对于奇数图像将等于 --_s
。
这是一个演示,说明了这个问题。悬停以查看 --_p
定义的偏移量是如何修复对齐的。
此外,请注意我们如何像在前面的示例中那样对第一个和最后一个图像使用不同的遮罩。我们只需要在第一个图像的右侧和最后一个图像的左侧使用锯齿。
为什么不使用圆角呢?让我们来试试!
我知道代码可能看起来很吓人,很难理解,但所有这些都是我们在这篇文章和其他我已分享的文章中介绍的不同技巧的组合。在这种情况下,我使用了与锯齿和倾斜形状相同的代码结构。将其与这些示例进行比较,您会发现没有区别!这些是在我之前关于缩放效果的文章中的相同技巧。然后,我使用我的其他文章和我的在线生成器来获取创建这些圆形形状的遮罩代码。
如果您还记得我们对锯齿所做的操作,我们会对所有图像使用相同的遮罩,然后必须向奇数图像添加偏移量以创建完美的重叠。在这种情况下,我们需要为奇数图像使用不同的遮罩。
第一个遮罩
mask:
linear-gradient(-90deg,#0000 calc(2*var(--s)),#000 0) var(--s),
radial-gradient(var(--s),#000 98%,#0000) 50% / calc(2*var(--s)) calc(1.8*var(--s)) space repeat;

第二个遮罩
mask:
radial-gradient(calc(var(--s) + var(--g)) at calc(var(--s) + var(--g)) 50%,#0000 98% ,#000)
calc(50% - var(--s) - var(--g)) / 100% calc(1.8*var(--s))

我在这里做的唯一工作是更新第二个遮罩以包含间隙变量 (--g
) 以在图像之间创建该空间。
最后的润色是修复第一个和最后一个图像。像所有前面的示例一样,第一个图像需要一个直的左边缘,而最后一个图像需要一个直的右边缘。
对于第一个图像,我们始终知道它需要具有的遮罩,如下所示。
.gallery > img:first-child {
mask:
radial-gradient(calc(var(--s) + var(--g)) at right, #0000 98%, #000) 50% / 100% calc(1.8 * var(--s));
}

对于最后一个图像,它取决于元素的数量,因此它取决于该元素是 :nth-child(odd)
还是 :nth-child(even)
。

.gallery > img:last-child:nth-child(even) {
mask:
linear-gradient(to right,#0000 var(--s),#000 0),
radial-gradient(var(--s),#000 98%,#0000) left / calc(2*var(--s)) calc(1.8*var(--s)) repeat-y
}

.gallery > img:last-child:nth-child(odd) {
mask:
radial-gradient(calc(var(--s) + var(--g)) at left,#0000 98%,#000) 50% / 100% calc(1.8*var(--s))
}
就是这样!三种不同的布局,但每次都使用相同的 CSS 技巧。
- 创建缩放效果的代码结构
- 用于创建形状的遮罩或剪辑路径
- 在某些情况下,奇数元素的单独配置,以确保我们有完美的重叠
- 第一个和最后一个图像的特定配置,以保持形状仅在一侧。
这是一个包含所有布局的大型演示。您只需要添加一个类即可激活要查看的布局。
这是使用 Flexbox 实现的版本。
总结
哦,我们完成了!我知道在这篇文章和上一篇文章之间有很多 CSS 技巧和示例,更不用说我在这里引用的其他所有技巧了,这些技巧来自我写的其他文章。我把所有东西整合在一起花费了一些时间,您不必立即理解所有内容。阅读一遍会让您对所有布局有一个很好的概述,但您可能需要阅读多遍文章并专注于每个示例才能掌握所有技巧。
您是否注意到除了标记中图像的数量之外,我们根本没有触及 HTML?我们创建的所有布局都共享相同的 HTML 代码,它只不过是一个图像列表。
在我结束之前,我将为您留下最后一个示例。它是两个动漫角色之间带有酷炫悬停效果的“对决”。
您呢?您可以根据您学到的知识创建一些东西吗?它不需要很复杂——像我用那个动漫对决所做的那样,想象一些很酷或有趣的东西。这可以成为您的一个很好的练习,我们最终可能会在评论区获得一个很棒的合集。
非常棒的效果
谢谢
CSS 变量应该具有有意义的名称。将变量命名为
--s
和--g
会降低代码的可维护性。许多业余程序员从本网站学习,应该避免此类反模式。正确命名变量是您的责任(而不是我的)。我没有给您提供可复制粘贴的代码,我正在详细说明 CSS 技巧以及如何使用它们以及它们背后的逻辑。在使用时,我也会详细说明每个变量的目的,因此我可以随意为它们命名,这无关紧要。
如果人们只是来复制粘贴代码,那不是我的错。他们首先需要理解代码,然后才能使用它。变量的命名将取决于您在实际项目中使用它们的位置。如果您只是采用我给出的名称,那毫无意义,因为它们可能会与您已经在使用的其他变量名称发生冲突。因此,在所有情况下,您都必须重命名它们。
很棒的系列。
只是注意到,在圆形图像网格上,如果您将鼠标悬停在每个圆圈周围一半标记处的边缘,图像无法确定是否要展开,只是闪烁。
你好
我非常喜欢这个,但是有没有办法将每个(动物)图片都做成链接?如果我尝试围绕 img 使用 a href,那么所有东西都会变得奇怪。