使用 CSS 渐变和纵横比创建响应式图像效果

已发表: 2022-03-10
快速总结 ↬ CSS 中的一个经典问题是保持图像在相关组件(例如卡片)之间的纵横比。 新支持aspect-ratio属性与object-fit相结合,为过去令人头疼的问题提供了一种补救措施! 让我们学习使用这些属性,以及创建响应式渐变图像效果以获得额外的天赋。

为了准备我们未来的图像效果,我们将设置一个卡片组件,顶部有一个大图像,后面是标题和描述。 这种设置的常见问题是我们可能并不总是完美地控制图像是什么,更重要的是我们的布局,它的尺寸是什么。 虽然这可以通过提前裁剪来解决,但由于容器大小的响应,我们仍然会遇到问题。 结果是卡片内容的位置不均匀,当你出示一排卡片时,它真的很突出。

除了裁剪之外,另一个先前的解决方案可能是从内联img交换到一个空白div ,该 div 仅存在于通过background-image呈现图像。 过去我自己多次实施过这个解决方案。 这样做的一个优点是使用一种较旧的纵横比技巧,该技巧使用零高度元素并设置padding-bottom值。 将填充值设置为百分比会产生相对于元素宽度的最终计算值。 您可能还使用这个想法来保持视频嵌入的 16:9 比例,在这种情况下,填充值可以通过以下公式找到: 9/16 = 0.5625 * 100% = 56.26% 。 但是我们将探索两个不涉及额外数学的现代 CSS 属性,给我们更多的灵活性,并且还允许通过使用真实的img而不是空的div来保持语义。

首先,让我们定义 HTML 语义,包括使用无序列表作为卡片的容器:

 <ul class="card-wrapper"> <li class="card"> <img src="" alt=""> <h3>A Super Wonderful Headline</h3> <p>Lorem ipsum sit dolor amit</p> </li> <!-- additional cards --> </ul>

接下来,我们将为.card组件创建一组最小的基线样式。 我们将为卡片本身设置一些基本的视觉样式,快速更新预期的h3标题,然后开始设置卡片图像样式的基本样式。

 .card { background-color: #fff; border-radius: 0.5rem; box-shadow: 0.05rem 0.1rem 0.3rem -0.03rem rgba(0, 0, 0, 0.45); padding-bottom: 1rem; } .card > :last-child { margin-bottom: 0; } .card h3 { margin-top: 1rem; font-size: 1.25rem; } img { border-radius: 0.5rem 0.5rem 0 0; width: 100%; } img ~ * { margin-left: 1rem; margin-right: 1rem; }

最后一条规则使用通用兄弟组合器为img之后的任何元素添加水平边距,因为我们希望图像本身与卡片的侧面齐平。

到目前为止,我们的进展导致我们出现以下卡片外观:

一张卡片应用了之前描述的基线样式,其中包括一张来自 Unsplash 的图片:小盘子上的甜点,旁边是杯子里的热饮
一张具有先前描述的基线样式的卡片应用并包括来自 Unsplash 的图像,该图像在小盘子上的甜点旁边是杯子里的热饮料。 (大预览)

最后,我们将使用 CSS 网格创建快速响应布局的.card-wrapper样式。 这也将删除默认列表样式。

 .card-wrapper { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(30ch, 1fr)); grid-gap: 1.5rem; }

注意如果您不熟悉这种网格技术,请查看我的教程中关于 12 列网格的现代解决方案的说明。

应用了这个并且所有卡片都包含具有有效源路径的图像,我们的.card-wrapper样式为我们提供了以下布局:

由于应用了卡片包装布局样式,三张卡片连续显示。每张卡片都有一个独特的图像,具有不同的自然纵横比,最后一张卡片的垂直方向图像是其他卡片图像高度的两倍以上
由于应用了卡片包装布局样式,三张卡片连续显示。 每张卡片都有一个独特的图像,具有不同的自然纵横比,最后一张卡片的垂直方向图像是其他卡片图像高度的两倍以上。 (大预览)

如预览图像所示,这些基线样式不足以正确包含图像,因为它们具有不同的自然尺寸。 我们需要一种方法来统一和一致地约束这些图像。

跳跃后更多! 继续往下看↓

启用具有object-fit统一图像大小

如前所述,您之前可能已在此场景中进行了更新,以更改要通过background-image添加的图像,并使用background-size: cover来很好地调整图像大小。 或者您可能已经尝试提前强制裁剪(这仍然是一个有价值的目标,因为任何图像尺寸减小都会提高性能!)。

现在,我们有了object-fit属性,它可以让img标签充当图像的容器。 并且,它还带有一个cover值,可以产生与背景图像解决方案类似的效果,但具有保留内联图像语义的好处。 让我们应用它,看看它是如何工作的。

我们确实需要将它与height尺寸配对,以获得关于我们希望图像容器如何表现的额外指导(回想一下我们已经添加了width: 100% )。 我们将使用max()函数来选择10rem30vh ,具体取决于给定上下文中哪个更大,这可以防止图像高度在较小的视口用户设置大缩放时缩小太多。

 img { /* ...existing styles */ object-fit: cover; height: max(10rem, 30vh); }

额外的辅助功能提示您应该始终在桌面上使用 200% 和 400% 缩放来测试您的布局。 虽然目前没有zoom媒体查询,但max()等函数可以帮助解决布局问题。 该技术有用的另一个上下文是元素之间的间距。

通过这次更新,我们确实改进了一些东西,视觉效果就好像我们使用了旧的背景图像技术:

三张卡片的图像现在看起来具有统一的高度,并且图像内容在图像中居中,就好像它是一个容器一样
三张卡片的图像现在看起来具有统一的高度,并且图像内容在图像中居中,就好像它是一个容器一样。 (大预览)

响应一致的图像大小与aspect-ratio

当单独使用object-fit时,一个缺点是我们仍然需要设置一些维度提示。

即将推出的属性(目前在 Chromium 浏览器中可用)称为aspect-ratio ,将增强我们一致地调整图像大小的能力。

使用这个属性,我们可以定义一个比率来调整图像的大小,而不是设置明确的尺寸。 我们将继续将它与object-fit结合使用,以确保这些尺寸仅影响作为容器的图像,否则,图像可能会出现失真。

这是我们完整更新的图像规则:

 img { border-radius: 0.5rem 0.5rem 0 0; width: 100%; object-fit: cover; aspect-ratio: 4/3; }

对于卡片上下文,我们将从43的图像比例开始,但您可以选择任何比例。 例如, 11表示正方形,或169表示标准视频嵌入。

这是更新后的卡片,尽管在这个特定实例中可能很难注意到视觉差异,因为纵横比恰好与我们通过单独设置object-fit height获得的外观非常匹配。

三张卡片的图像具有相同的宽度和高度尺寸,这与之前的 object-fit 解决方案略有不同
三张卡片图像具有相同的宽度和高度尺寸,这与之前的对象拟合解决方案略有不同。 (大预览)

设置 `aspect-ratio` 会导致比例随着元素的增长或缩小而保持不变,而当仅设置 `object-fit` 和 `height` 时,图像比例将随着容器尺寸的变化而不断变化。

使用 CSS 渐变和函数添加响应式效果

好的,现在我们知道了如何设置一致大小的图像,让我们通过添加渐变效果来享受它们的乐趣!

我们使用此效果的目标是让它看起来好像图像正在淡入卡片内容。 您可能很想将图像包装在自己的容器中以添加渐变,但是由于我们已经在图像大小方面所做的工作,我们可以弄清楚如何在主.card上安全地执行此操作。

第一步是定义梯度。 我们将使用 CSS 自定义属性添加渐变颜色,以便轻松交换渐变效果,从蓝色到粉红色开始。 渐变中的最后一种颜色将始终为白色,以保持过渡到卡片内容背景并创建“羽化”边缘。

 .card { --card-gradient: #5E9AD9, #E271AD; background-image: linear-gradient( var(--card-gradient), white max(9.5rem, 27vh) ); /* ...existing styles */ }

但是等等——这是一个 CSS max()函数吗? 在渐变中? 是的,这是可能的,而且它是使这个渐变响应有效的魔力!

但是,如果我要添加屏幕截图,我们实际上不会看到渐变对图像有任何影响。 为此,我们需要引入mix-blend-mode属性,在这种情况下,我们将使用overlay值:

 img { /* ...existing styles */ mix-blend-mode: overlay; }

mix-blend-mode属性类似于应用 Photoshop 等照片处理软件中可用的图层混合样式。 并且overlay值将具有允许图像中的中等色调与其后面的渐变混合的效果,从而导致以下结果:

每张卡片图像都有渐变混合效果,从顶部的浅蓝色开始,混合到红粉红色,然后在卡片文本内容的其余部分之前羽化为白色
每张卡片图像都有一个渐变混合效果,从顶部的浅蓝色开始,混合到微红色的粉红色,然后在卡片文本内容的其余部分之前羽化成白色。 (大预览)

现在,此时,我们仅依靠aspect-ratio来调整图像大小。 如果我们调整容器大小并导致卡片布局重排,则不断变化的图像高度会导致渐变渐变为白色的位置不一致。

因此,我们还将添加一个max-height属性,该属性使用max()函数并包含比渐变中的值稍大的值。 由此产生的行为是渐变将(几乎总是)正确地与图像底部对齐。

 img { /* ...existing styles */ max-height: max(10rem, 30vh); }

需要注意的是,添加 `max-height` 会改变 `aspect-ratio` 行为。 它不会总是使用精确的比例,而是仅在给定“最大高度”的新额外约束时有足够的分配空间时使用。

但是, aspect-ratio仍将继续确保图像大小一致地调整,这与仅object-fit相比具有优势。 尝试在最终的 CodePen 演示中注释掉aspect-ratio ,以查看它在不同容器大小之间的差异。

由于我们最初的目标是实现一致的响应式图像尺寸,因此我们仍然达到了目标。 对于您自己的用例,您可能需要调整比率和高度值以达到您想要的效果。

替代方案: mix-blend-mode并添加过滤器

使用overlay作为mix-blend-mode值是我们正在寻找的淡入白效果的最佳选择,但让我们尝试另一种选择以获得更戏剧性的效果。

我们将更新我们的解决方案,为mix-blend-mode值添加 CSS 自定义属性,并更新渐变的颜色值:

 .card { --card-gradient: tomato, orange; --card-blend-mode: multiply; } img { /* ...existing styles */ mix-blend-mode: var(--card-blend-mode); }

multiply值对中间色调有暗化效果,但保持白色和黑色不变,从而产生以下外观:

每张卡片图像都具有从红橙色到纯橙色的新渐变的强烈橙色色调。白色区域仍然是白色,黑色区域仍然是黑色
每张卡片图像都具有从红橙色到纯橙色的新渐变的强烈橙色色调。 白色区域仍然是白色,黑色区域仍然是黑色。 (大预览)

虽然我们已经失去了淡入淡出并且现在在图像底部有一个硬边,但我们渐变的白色部分仍然很重要,以确保渐变在卡片内容之前结束。

我们可以添加的另一个修改是使用filter ,特别是使用grayscale()函数来去除图像颜色,因此渐变是图像着色的唯一来源。

 img { /* ...existing styles */ filter: grayscale(100); }

使用grayscale(100)的值会导致完全去除图像的自然颜色并将其转换为黑白。 这是与之前使用我们的橙色渐变和multiply效果的屏幕截图进行比较的更新:

现在每个卡片图像仍然具有橙色渐变,但所有其他颜色都被删除并被灰色阴影代替
现在每个卡片图像仍然具有橙色渐变,但所有其他颜色都被移除并被灰色阴影取代。 (大预览)

使用aspect-ratio增强

如前所述,目前仅最新版本的 Chromium 浏览器(Chrome 和 Edge)支持aspect-ratio 。 然而,所有的浏览器都支持object-fit ,再加上我们的height限制,导致了一个不太理想但仍然可以接受的结果,在这里可以看到 Safari:

卡片图像高度有上限,但每张卡片的实际高度略有不同
卡片图像高度有上限,但每张卡片的实际高度略有不同。 (大预览)

如果没有aspect-ratio功能,这里的结果是最终图像高度被限制,但每个图像的自然尺寸仍然导致卡片图像高度之间的一些差异。 您可能希望改为添加max-height或再次使用max()函数来帮助使max-height卡片尺寸下更具响应性。

扩展渐变效果

由于我们将渐变色标定义为 CSS 自定义属性,因此我们可以随时在不同的上下文中更改它们。 例如,如果卡片悬停或其中一个子元素处于焦点位置,我们可能会更改渐变以更强烈地显示其中一种颜色。

首先,我们将更新每张卡片h3以包含一个链接,例如:

 <h3><a href="">A Super Wonderful Headline</a></h3>

然后,我们可以使用我们最新的可用选择器之一 - :focus-within - 在链接处于焦点时更改卡片渐变。 为了额外覆盖可能的交互,我们将把它与:hover结合起来。 而且,我们将重用我们的max()想法来分配一种颜色来接管卡片图像部分的覆盖范围。 这种特殊效果的缺点是渐变停止和颜色变化不能可靠地动画化——但由于 CSS Houdini,它们很快就会实现。

要更新颜色并添加新的色标,我们只需要在这个新规则中重新分配--card-gradient的值:

 .card:focus-within, .card:hover { --card-gradient: #24a9d5 max(8.5rem, 20vh); }

我们的max()值小于用于white以保持羽化边缘的原始值。 如果我们使用相同的值,它将遇到white并创建一个清晰的直尺分离。

在创建这个演示时,我最初尝试了一个使用带有scaletransform来实现放大效果的效果。 但我发现,由于应用了mix-blend-mode ,浏览器不会始终如一地重新绘制图像,从而导致令人不快的闪烁。 请求浏览器执行纯 CSS 效果和动画总是需要权衡取舍,虽然我们可以做的很酷,但最好检查效果对性能的影响。

体验愉快!

现代 CSS 为我们提供了一些很棒的工具来更新我们的网页设计工具包,其中最新添加的aspect-ratio 。 因此,继续尝试object-fitaspect-ratio ,并在渐变中添加诸如max()之类的函数,以获得一些有趣的响应效果! 请务必仔细检查跨浏览器(现在!)以及不同的视口和容器大小。

这是 CodePen,包括我们今天回顾的功能和效果:

请参阅 Stephanie Eckles 的 Pen [Responsive Image Effects with CSS Gradients and aspect-ratio](https://codepen.io/smashingmag/pen/WNoERXo)。

请参阅 Stephanie Eckles 的具有 CSS 渐变和纵横比的 Pen Responsive Image Effects。

寻找更多? 确保您在 Smashing 上查看我们的 CSS 指南 →