在网格布局中缩放图片

Avatar of Temani Afif
Temani Afif

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

借助 CSS Grid,创建图片网格非常容易。但是,在图片放置后让网格执行一些花哨的操作可能很棘手。

假设您想为图片添加一些花哨的悬停效果,使其在悬停时超出其所在行和列进行增长和缩放?我们可以做到!

很酷,对吧?如果您检查代码,您将不会找到任何 JavaScript、复杂选择器,甚至 神奇数字。这只是我们将探索的众多示例之一!

构建网格

创建网格的 HTML 代码与容器内的图片列表一样简单。我们不需要更多内容。

<div class="gallery">
  <img>
  <img>
  <img>
  <!-- etc. -->
</div>

对于 CSS,我们首先通过设置以下内容来开始网格

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

  display: grid;
  gap: var(--g);
  width: calc(3*var(--s) + 2*var(--g)); /* 3 times the size plus 2 times the gap */
  aspect-ratio: 1;
  grid-template-columns: repeat(3, auto);
}

简而言之,我们有两个变量,一个控制图片的大小,另一个设置图片之间的间隙。 aspect-ratio 有助于保持比例。

您可能想知道为什么我们只定义了三列,而没有定义行。不,我没有忘记行 - 我们只是不需要显式地设置它们。CSS Grid 能够自动将项目放置在 隐式行和列 上,这意味着我们可以根据我们抛出的任何数量的图片获得所需的任何数量的行。我们可以显式地定义行,但我们需要添加 grid-auto-flow: column 以确保浏览器会为我们创建所需的列。

以下是一个说明两种情况的示例。区别在于一个是 row 方向流动,另一个是 column 方向流动。

查看 我写的这篇关于隐式网格和自动放置算法的文章,了解更多信息。

现在我们有了网格,该为图片设置样式了

.gallery > img {
  width: 0;
  height: 0;
  min-height: 100%;
  min-width: 100%;
  object-fit: cover;
}

我们正在制作的悬停效果依赖于此 CSS。您可能觉得我们制作的图片既没有宽度也没有高度,但最小宽度和高度为 100% 显得有点奇怪。但您会看到,对于我们想要实现的目标,这是一个很巧妙的技巧。

我在这里告诉浏览器,图片需要具有 0 宽度和高度,但也需要具有等于 100% 的最小高度……但 100% 是相对于什么的?使用百分比时,该值是 相对于其他内容。在这种情况下,我们的图片放置在 网格单元 内,我们需要知道该大小才能知道 100% 是相对于什么。

浏览器将首先忽略 min-height: 100% 来计算网格单元格的大小,但它将在计算中使用 height: 0。这意味着我们的图片不会影响网格单元格的大小……因为它们在技术上没有物理尺寸。这将导致三个相等的列和行,这些列和行基于网格的大小(我们在 .gallery 的宽度和 aspect-ratio 上定义)。每个网格单元格的高度只不过是我们定义的变量 --s(宽度也是如此)。

现在我们有了网格单元格的尺寸,浏览器将使用它与 min-height: 100%(以及 min-width: 100%)一起使用,这将强制图片完全填充每个网格单元格的空间。整个过程可能看起来有点令人困惑,但主要思想是确保网格定义图片的大小,而不是相反。我不想让图片定义网格的大小,您将在添加悬停效果后明白为什么。

创建悬停效果

我们需要做的是在悬停时增加图片的比例。我们可以通过调整图片的 widthheight 来实现 :hover

.gallery {
  --f: 1.5; /* controls the scale factor */
}

.gallery img:hover{
  width:  calc(var(--s) * var(--f));
  height: calc(var(--s) * var(--f));
}

我在混合中添加了一个新的自定义变量 --f 作为比例因子来控制悬停时的尺寸。请注意,我如何将其乘以大小变量 --s 来计算新的图片尺寸。

但你说图片大小必须为 0。怎么回事?我迷路了……

我所说的仍然是事实,但我正在为悬停的图片设置一个例外。我告诉浏览器,只有一张图片的尺寸不等于零——因此它将影响网格的尺寸——而所有其他图片都保持等于 0

左侧显示了网格的自然状态,没有任何悬停图片,这与右侧显示的内容相同。左侧的所有网格单元格尺寸相同,因为所有图片都没有物理尺寸。

在右侧,第一行中的第二张图片被悬停,这使其具有影响网格单元格尺寸的尺寸。浏览器将在悬停时使该特定网格单元格变大,这将影响整体尺寸。由于整个网格的尺寸已设置(因为我们在 .gallery 上设置了固定的 width),其他网格单元格将逻辑上响应,通过变小来保持 .gallery 的整体尺寸不变。

这就是我们的缩放效果在起作用!通过仅增加一张图片的尺寸,我们影响了整个网格配置,我们之前说过网格定义图片的大小,因此每张图片在网格单元格内拉伸以填充所有空间。

为此,我们添加了一点 transition 并使用 object-fit 来避免图片失真,并且错觉是完美的!

我知道该技巧背后的逻辑并不容易理解。如果您没有完全理解它,请不要担心。最重要的是要了解所用代码的结构以及如何修改它以获得更多变化。这就是我们接下来要做的!

添加更多图片

我们创建了一个 3×3 网格来解释主要技巧,但您可能已经猜到我们不需要止步于此。我们可以使列数和行数成为变量,并添加任意数量的图片。

.gallery {
  --n: 3; /* number of rows*/
  --m: 4; /* number of columns */
  --s: 150px; /* control the size */
  --g: 10px;  /* control the gap */
  --f: 1.5;   /* control the scale factor */

  display: grid;
  gap: var(--g);
  width:  calc(var(--m)*var(--s) + (var(--m) - 1)*var(--g));
  height: calc(var(--n)*var(--s) + (var(--n) - 1)*var(--g));
  grid-template-columns: repeat(var(--m),auto);
}

我们有两个用于行数和列数的新变量。然后我们只需使用它们来定义网格的宽度和高度。grid-template-columns 也是如此,它使用 --m 变量。和以前一样,我们不需要显式地定义行,因为 CSS Grid 的自动放置功能会为我们完成这项工作,无论我们使用多少个图片元素。

为什么宽度和高度不能使用不同的值?我们可以做到

.gallery {
  --n: 3; /* number of rows*/
  --m: 4; /* number of columns */
  --h: 120px; /* control the height */
  --w: 150px; /* control the width */
  --g: 10px;  /* control the gap */
  --f: 1.5;   /* control the scale factor */

  display: grid;
  gap: var(--g);
  width:  calc(var(--m)*var(--w) + (var(--m) - 1)*var(--g));
  height: calc(var(--n)*var(--h) + (var(--n) - 1)*var(--g));
  grid-template-columns: repeat(var(--m),auto);
}

.gallery img:hover{
  width:  calc(var(--w)*var(--f));
  height: calc(var(--h)*var(--f));
}

我们用两个变量替换 --s,一个是宽度 --w,另一个是高度 --h。然后我们相应地调整其他所有内容。

因此,我们从一个具有固定尺寸和元素数量的网格开始,但随后我们创建了一组新的变量来获得我们想要的任何配置。我们所要做的就是添加任意数量的图片,并相应地调整 CSS 变量。组合是无限的!

全屏版本怎么样?是的,这也是可能的。我们只需要知道需要为我们的变量分配哪些值。如果我们想要 N 行图片,并且我们希望网格全屏显示,那么我们首先需要求解一个高度为 100vh 的值

var(--n) * var(--h) + (var(--n) - 1) * var(--g) = 100vh

宽度也是相同的逻辑,但使用 vw 而不是 vh

var(--m) * var(--w) + (var(--m) - 1) * var(--g) = 100vw

我们进行计算以获得

--w: (100vw - (var(--m) - 1) * var(--g)) / var(--m)
--h: (100vh - (var(--n) - 1) * var(--g)) / var(--n)

完成!

它与相同的 HTML 相同,但具有一些更新的变量,这些变量更改了网格的大小和行为。

请注意,我省略了我们之前在 .gallerywidthheight 上设置的公式,并分别用 100vw100vh 替换了它们。该公式将为我们提供相同的结果,但由于我们知道我们想要的值,因此我们可以放弃所有这些额外的复杂性。

我们还可以通过从等式中删除间隙来简化 --h--w,转而使用以下内容

--h: calc(100vh / var(--n)); /* Viewport height divided by number of rows */
--w: calc(100vw / var(--m)); /* Viewport width divided by number of columns */

这将使悬停的图片比之前的示例增长更多,但这并不重要,因为我们可以使用 --f 变量作为乘数来控制比例。

由于变量在一个地方使用,因此我们还可以通过完全删除它们来简化代码

重要的是要注意,此优化仅适用于全屏示例,而不适用于我们已经讨论过的示例。此示例是一个特殊情况,我们可以通过删除其他示例中需要的一些复杂计算工作来使代码更轻。

我们实际上拥有创建流行的展开面板模式所需的一切

让我们深入挖掘

你注意到我们的缩放因子可以小于1吗?我们可以定义悬停图像的大小小于--h--w,但图像在悬停时会变大。

初始网格单元格大小等于--w--h,那么为什么较小的值会使网格单元格变大?单元格不应该变小,或者至少保持其初始大小?那么网格单元格的最终大小是多少呢?

我们需要深入了解 CSS Grid 算法如何计算网格单元格的大小。这涉及理解 CSS Grid 的默认拉伸对齐

以下是一个例子来理解这个逻辑。

在演示的左侧,我定义了一个两列的网格,宽度为auto。我们得到了直观的結果:两列相等(以及两个相等的网格单元格)。但是我在演示的右侧设置的网格,其中我使用place-content: start更新了对齐方式,似乎什么都没有。

DevTools 帮助我们显示了这两种情况下真正发生的事情。

在第二个网格中,我们有两列,但它们的宽度都为零,因此我们得到了两个折叠在网格容器左上角的网格单元格。这不是一个错误,而是网格对齐的逻辑结果。当我们用auto设置列(或行)的大小,这意味着它的内容决定了它的大小——但我们有一个空的div,没有内容来为它腾出空间。

但是,由于stretch是默认的对齐方式,并且我们在网格内部有足够的空,浏览器将平等地拉伸两个网格单元格,以覆盖所有该区域。这就是左侧的网格最终得到两个相等列的原因。

来自规范

请注意,某些 justify-contentalign-content 的值会导致轨道间隔开 (space-aroundspace-betweenspace-evenly) 或调整大小 (stretch)。

请注意“调整大小”,这是关键所在。在最后一个例子中,我使用了place-content,它是justify-contentalign-content的简写。

这隐藏在 Grid Sizing 算法 规范中的某个地方。

此步骤通过将任何剩余的正 确定性 空闲空间 均匀分配到它们之间,来扩展具有 auto 最大轨道大小函数 的轨道。如果空闲空间是 不确定性 的,但 网格容器 具有确定性的 最小宽度/高度,则使用该大小来计算此步骤的空闲空间。

“均等”解释了为什么我们最终得到了相等的网格单元格,但它适用于“空闲空间”,这一点非常重要。

让我们以之前的例子为例,并在其中一个div中添加内容。

我们添加了一个方形的50px图像。以下是如何解释我们示例中的每个网格对该图像的响应。

在第一种情况下,我们可以看到第一个单元格(红色)比第二个单元格(蓝色)更大。在第二种情况下,第一个单元格的大小会改变以适应图像的实际大小,而第二个单元格保持没有尺寸。空闲空间被平均分配,但第一个单元格内部有更多内容,这使得它更大。

这是计算空闲空间的数学公式。

(grid width) - (gap) - (image width) = (free space)
200px - 5px - 50px = 145px 

除以 2(列数),我们得到每列 72.5px 的宽度。但是我们将图像大小 50px 添加到第一列,这使得我们得到一列 122.5px 和第二列 72.5px

相同的逻辑也适用于我们的图像网格。所有图像的大小都等于0(无内容),而悬停图像会影响大小——即使它只是1px——使其网格单元格比其他单元格更大。因此,缩放因子可以是大于0的任何值,甚至可以是01之间的十进制数。

要获得网格单元格的最终宽度,我们进行相同的计算,得到以下结果。

(container width) - (sum of all gaps) - (hovered image width) = (free space)

容器的宽度由以下公式定义。

var(--m)*var(--w) + (var(--m) - 1)*var(--g)

… 所有间隙都等于

(var(--m) - 1)*var(--g)

… 对于悬停图像,我们有

var(--w)*var(--f)

我们可以使用我们的变量计算所有这些。

var(--m)*var(--w) - var(--w)*var(--f) = var(--w)*(var(--m) - var(--f))

列数由--m定义,因此我们将空闲空间平均分配,得到

var(--w)*(var(--m) - var(--f))/var(--m)

… 这给了我们非悬停图像的大小。对于悬停图像,我们有这个。

var(--w)*(var(--m) - var(--f))/var(--m) + var(--w)*var(--f)
var(--w)*((var(--m) - var(--f))/var(--m) + var(--f))

如果我们想控制悬停图像的最终大小,我们可以考虑上面的公式,以获得我们想要的确切大小。例如,如果我们想要图像大小是原来的两倍。

(var(--m) - var(--f))/var(--m) + var(--f) = 2

因此,我们的缩放倍数--f的值需要等于

var(--m)/(var(--m) - 1)

对于三列,我们将有3/2 = 1.5,这是我在本文第一个演示中使用的缩放因子,因为我想在悬停时使图像大小是原来的两倍!

相同的逻辑适用于高度计算,如果我们想要独立地控制它们,我们需要考虑两个缩放因子,以确保我们有特定的悬停宽度和高度。

.gallery {
  /* same as before */
   --fw: 1.5; /* controls the scale factor for the width */
   --fh: 1.2; /* controls the scale factor for the height */

  /* same as before */
}

.gallery img:hover{
  width:  calc(var(--w)*var(--fw));
  height: calc(var(--h)*var(--fh));
}

现在,您已经知道了所有创建任何类型的图像网格(具有酷炫的悬停效果)的秘密,同时还能够使用我们刚刚介绍的数学公式来控制您想要的大小。

总结

在我的上一篇文章中,我们使用了几行 CSS 代码创建了一个看起来很复杂的网格,这些代码利用了 CSS Grid 的隐式网格和自动放置功能。在本文中,我们依赖于一些 CSS Grid 大小技巧来创建一个奇特的图像网格,这些图像在悬停时会放大,并导致网格相应地调整。所有这些都使用简化的代码,可以轻松使用 CSS 变量进行调整!

在下一篇文章中,我们将玩转形状!我们将结合 CSS 网格、蒙版和剪切路径来获得奇特的图像网格。