好吧,我们上次来的时候,我们正在使用CSS Grid,并将它们与CSSclip-path 和mask 技术结合起来,以创建具有花哨形状的网格。
这里只是我们一起制作的奇妙网格之一。
CodePen嵌入回退
准备好进行第二轮了吗?我们仍在使用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>
和上一篇文章一样,我们只需要一个里面有图片的容器。没有别的了!
嵌套图像网格
上次,我们的网格是,嗯,典型的图像网格。除了我们用整齐的形状掩盖它们之外,就我们如何定位里面的图像而言,它们是相当标准的对称网格。
让我们试试在网格的中心嵌套一个图像。
CodePen 嵌入回退
我们首先为四张图片设置一个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));
}
现在,你们中的许多人可能也想知道:既然我们可以把最后一张图片放在上面,并给它加一个边框,为什么还要做这么复杂的事情?这样就可以把嵌套图片下面的图片隐藏起来,不需要遮挡,对吗?
这是真的,我们将得到以下结果。
CodePen嵌入回退
没有mask ,没有clip-path 。是的,这段代码很容易理解,但有一个小缺点:边框的颜色需要与主背景相同,才能使幻觉完美。这个小缺点足以让我把代码变得更复杂,以换取独立于背景的真正的透明度。我并不是说边框的方法是坏的或错误的。在大多数情况下,如果背景是已知的,我会推荐它。但我们在这里是为了探索新的东西,最重要的是,建立不依赖环境的组件。
这次让我们尝试另一种形状。
CodePen嵌入回退
这一次,我们使嵌套的图像成为一个圆形,而不是一个正方形。这是个很容易的任务,用 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 hack,以便能够对径向梯度进行动画处理。
如果你检查梯度的代码,你可以看到我正在添加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 。
另一个形状?我们走吧!
CodePen嵌入回退
这一次,我们将嵌套的图片剪成菱形的形状。我会让你把代码作为练习来剖析,以弄清我们是如何到达这里的。你会注意到,结构与我们的例子中相同。唯一的区别是我们如何使用梯度来创建形状。仔细研究并学习吧
圆形图像网格
我们可以结合我们在这里和以前的文章中所学到的知识,制作一个更令人兴奋的图像网格。这一次,让我们把我们的网格中的所有图片都做成圆形,并且在悬停时,展开一张图片来显示整个图片,因为它覆盖了其他的照片。
CodePen嵌入回退
网格的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%));
}
注意我们是如何将clip-path 的值定义为var() 内的回退。这种方式允许我们通过设置--_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 的简写,用于水平和垂直放置元素。当它取一个值时,两种排列方式都使用同一个值。
展开图像面板
在之前的文章中,我创建了一个很酷的缩放效果,适用于图片的网格,我们可以控制一切:行数、列数、尺寸、比例系数等等。
一个特殊的情况是经典的扩展面板,我们只有一排和一个全宽的容器。
CodePen嵌入回退
我们将以这个例子为例,并将其与形状结合起来!
在我们继续之前,我强烈建议阅读我的另一篇文章,以了解我们即将涉及的技巧如何工作。请看那篇文章,我们将在这里继续专注于创建面板形状。
首先,让我们从简化代码和删除一些变量开始
CodePen嵌入回退
我们只需要一行,列的数量应该根据图片的数量来调整。这意味着我们不再需要行数(--n)和列数(--m )的变量,但我们需要使用grid-auto-flow: column ,允许网格在我们添加新图片时自动生成列。我们将为我们的容器考虑一个固定的高度;默认情况下,它将是全宽的。
让我们把图片剪辑成一个倾斜的形状:

clip-path: polygon(S 0%, 100% 0%, (100% - S) 100%, 0% 100%);
CodePen嵌入回退
再一次,每张图片都包含在其网格单元中,所以图片之间的空间比我们希望的要大。

我们需要增加图片的宽度,以创造一个重叠的空间。我们用min-width: calc(100% + var(--s)) 替换min-width: 100% ,其中--s 是一个控制形状的新变量。
CodePen嵌入回退
现在我们需要修复第一张和最后一张图片,使它们在没有间隙的情况下渗出页面。换句话说,我们可以去除第一张图片左边的斜线和最后一张图片右边的斜线。我们需要一个新的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%);
}
最后的结果是一个很好的倾斜图片扩展面板!
CodePen嵌入回退
我们可以根据你的需要添加更多的图片,网格会自动调整。另外,我们只需要控制一个值就可以控制形状了!
我们可以用flexbox做这个同样的布局,因为我们处理的是单行元素。下面是我的实现。
当然,倾斜的图像是很酷的,但是人字形图案呢?我已经在上一篇文章的结尾处预告了这个问题。
CodePen嵌入回退
我在这里所做的是用clip-path ,而不是mask...你猜怎么着?我已经有一篇关于创建 "之 "字形的详细文章 - 更不用说一个在线生成器来获得代码。看到所有的东西是如何组合在一起的吗?
这里最棘手的部分是确保 "之 "字形完全对齐,为此,我们需要为每个: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 定义--是如何修复对齐的。
CodePen嵌入回退
此外,注意到我们如何为第一张和最后一张图片使用不同的掩码,就像我们在前面的例子中做的那样。我们只需要在第一张图片的右侧和最后一张图片的左侧做一个 "之 "字形。
而且,为什么不是圆边呢?让我们来做吧!
CodePen嵌入回退
我知道这段代码可能看起来很吓人,也很难理解,但所有这些都是我们在这篇和其他文章中已经分享过的不同技巧的组合。在这种情况下,我使用的代码结构与 "之 "字形和斜线形相同。将它与那些例子进行比较,你会发现没有任何区别这些都是我之前文章中关于缩放效果的技巧。然后,我正在使用我的其他写作和我的在线生成器来获得创建那些圆形形状的遮罩的代码。
如果你还记得我们为 "之 "字形所做的工作,我们曾对所有的图像使用相同的蒙版,但后来不得不对奇数的图像添加偏移量,以创造一个完美的重合。在这种情况下,我们需要为奇数的图像使用不同的蒙版。
第一个蒙版
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技巧。
- 创建缩放效果的代码结构
- 用遮罩或剪辑路径来创建形状
- 在某些情况下为奇数元素单独配置,以确保我们有一个完美的重叠。
- 对第一张和最后一张图片进行特定的配置,以保持形状只在一边。
这里有一个大的演示,所有这些都在一起。你所需要的只是添加一个类来激活你想看到的布局。
CodePen嵌入回退
而这里是有Flexbox实现的那个
CodePen 嵌入回退
收尾工作
哦,我们完成了!我知道在这篇文章和上一篇文章之间有许多CSS技巧和例子,更不用说我在这里引用了我写的其他文章中的所有其他技巧。我花了一些时间把所有的东西放在一起,你不需要一下子就理解所有的东西。一次阅读就能让你对所有的布局有一个很好的概述,但你可能需要多读几遍文章,关注每一个例子,以掌握所有的技巧。
你是否注意到,除了标记中的图片数量之外,我们根本就没有触及HTML?我们所做的所有布局都共享相同的HTML代码,这只不过是一个图片列表。
在我结束之前,我将给你留下最后一个例子。这是两个动漫人物之间的 "对决",有一个很酷的悬停效果。
CodePen嵌入回退
你呢?你能根据你所学的知识创造一些东西吗?它不需要很复杂--想象一些很酷或很有趣的东西,就像我做的那个动漫对决。这对你来说可以是一个很好的锻炼,我们可能会在评论区以一个优秀的集合结束。