闪亮、透视和旋转:精美的 CSS 3D 图像效果

本文译者为 360 奇舞团前端开发工程师

原文标题:Shines, Perspective, And Rotations: Fancy CSS 3D Effects For Images

原文链接:https://www.smashingmagazine.com/2023/07/shines-perspective-rotations-css-3d-effects-images/#comments-shines-perspective-rotations-css-3d-effects-images

原文作者: Temani Afif

闪光、视差和旋转:精美的 CSS 3D 图像效果

摘要:CSS 拥有各种技巧,能够将图像变成简洁的交互式元素。本文收集了一系列精美的 3D 图像效果,展示了这些 CSS 的强大功能。准备好了解它们的工作原理吧,我们将通过使用CSS的功能来为图像添加透视、深度、旋转,甚至光滑的光泽效果,这些效果可以在你的下一个项目中使用。

我们都认为 3D 效果很酷,对吧?我也这么认为,尤其是当它们与微妙的动画结合在一起时。在本文中,我们将探索一些 CSS 技巧来创建令人惊叹的 3D 效果!

“为什么我们还需要一篇关于 CSS 3D 效果的文章……不是已经有很多了吗?” 是的,但这篇文章有点特别,因为我们将使用尽可能少的 HTML。实际上,这是我们用来为图像制作一些非常惊人的 CSS 效果的唯一标记:

<img src="" alt="">

就是这样!我们只需要一个 <img> 标签。其他的一切都将在CSS中完成。

下面是它的工作原理。我们将探索三种不同的效果,它们彼此之间没有联系,但可能会互相借鉴一些。你不需要一口气读完整篇文章。实际上,我建议一次阅读一个部分,花时间理解概念和底层代码的作用,然后再继续查看另一种效果。

目录

  • CSS 3D 闪光

  • CSS 3D 视差

  • CSS 3D 旋转

CSS 3D 闪光

对于第一个效果,我们将在图像上添加一种闪光的动画,并在鼠标悬停时稍微旋转。

be9d52276ef1478063bd74de59115862.gif

codepen链接:https://codepen.io/t_afif/pen/VwEJqKV

看到了吗?图像在开始时略微倾斜,但在悬停时会自动恢复水平,同时表面反射出光泽。这是一种很好的方式,可以在UI界面中增加一些逼真感,而不会过分夸张。

在这个演示中,我首先在CSS中给图像添加了旋转效果:

img {
  transform: perspective(400px) rotate3d(1, -1, 0, 8deg);
}
img:hover {
  transform: perspective(400px) rotate3d(1, -1, 0, -8deg);
}

rotate3d允许我们定义图像旋转的轴线。我不会深入讲解具体的数学细节,但为了获得一个对角轴线,我们将z轴设为0,并在x轴和y轴上使用1-1

然后,我们使用perspective属性为图像添加一点不平衡感。400px这个值或应用于旋转的值8deg这两个值没有特定的逻辑,但我发现小角度结合大透视效果会产生不错的结果。你可以随意修改它们,也许你会为你的特定用例找到更好的值。

我们可以简化这个过程!为了避免在:hover时重复编写代码,我们可以使用CSS变量在悬停时更新旋转角度的符号。这样,我们就不需要重新编写整个声明,只需将8deg更改为-8deg即可。

img {
  transform: perspective(400px) rotate3d(1, -1, 0, calc(var(--i, 1) * 8deg));
}
img:hover {
  --i: -1;
}

请注意我在代码中使用了 calc()。通过将度数值乘以1(由变量 --i 定义),我们得到默认值8deg。然后,通过在悬停时将1更改为-1,让 calc() 完成繁重的计算工作。

这很有趣!但当我们开始处理光泽效果时,它变得更加有趣。一个直观的方法是在图像上方放置一个覆盖层来制作光泽效果。但请记住,我们只使用一个单独的 <img> 元素,添加覆盖层需要更多的标记。

你可能会想到使用伪元素。但很遗憾,在这里伪元素不适用于 <img> 标签。

我们要做的是使用CSS遮罩和动画渐变来“模拟”闪光效果。我说“模拟”是因为,实际上,你看到的图像是部分透明的。在悬停时,透明度会更新以创建出闪亮的效果。

我知道这并不容易理解,但如果你考虑到我们的背景是黑色的 ,是的,这就是技巧的一部分!使图像部分透明类似于使图像变暗。当图像被悬停时,我们调整透明度使其变亮。

以下是一个使用opacity来更好理解我所说的内容的简化示例:

c32299c345224375bbe8c3f5ca20d83d.gif

codepen链接:https://codepen.io/t_afif/pen/RwqNjeo

这就是基本的想法。现在,我们将使用linear-gradient()遮罩来实现闪光效果。

/* 
 *对角渐变,在中心部分不透明,在两侧半透明。
*/
mask: linear-gradient(135deg, #000c 40%, #000, #000c 60%);

在CSS中进行遮罩处理时,颜色并不重要,因为默认的遮罩模式会自动处理。关键是透明度通道,它决定了透明的程度。在我们的例子中,对角部分是不透明的,而两侧是部分透明的。#000c相当于rgb(0 0 0 / 80%).

2e18b492dc3c838e0567289213894c18.png

codepen链接:https://codepen.io/t_afif/pen/VwVYrVa

渐变效果非常微妙,因为我们只稍微减少了透明度。这是一件好事,因为我们不希望用户注意到图像默认情况下是部分透明的。

下一步是对渐变效果进行动画处理。我们增加它的尺寸,直到不透明的中心超出视野范围。然后,我们将其从图像的左上角移动到右下角

img {
  mask:
    linear-gradient(135deg, #000c 40%, #000, #000c 60%)
    100% 100%/ /* 初始位置 右下角 */
    240% 240%; /* 宽 高 */
}
img:hover {
    mask-position: 0 0; /* 在悬停时移动到左上角 */
}

来看一下吧!我们在悬停时有一个漂亮的闪光效果!

7dd1b64a791287fd6e3295e7c465cc35.gif

codepen链接:https://codepen.io/t_afif/pen/eYQmebX

很酷,对吧?现在让我们将上面的闪光效果与3D旋转相结合,以获得完整的效果。

img {
  transform: perspective(400px) rotate3d(1,-1,0,calc(var(--i,1)*8deg));
  mask:
    linear-gradient(135deg,#000c 40%,#000,#000c 60%)
    100% 100%/240% 240%;
  transition: .4s;
  cursor: pointer;
}
img:hover {
  --i: -1;
  mask-position: 0 0;
}

只需一个HTML元素和几行CSS代码,我们就可以实现这个效果。下面是一个图示,用来说明遮罩中使用的不同数值:

1edaeb4b10881a8ee980ae4676ad865e.png
*演示 CSS 遮罩如何覆盖图像以及图像如何在悬停时滑动。*

演示 CSS 遮罩如何覆盖图像以及图像如何在悬停时滑动。

绿色框表示了渐变区域,蓝色线条定义了我们使用的颜色位置。初始状态下,渐变框被放置在100% 100%的位置,悬停时,我们将其滑动到0 0的位置。滑动效果将沿着图像移动渐变的对角部分(不透明部分),从而创建闪光效果。

这是完整的演示,我甚至为你提供了第二个变体样式,供你详细研究并了解其工作原理。

8ac25643d17caea45bc9985722f26c71.gif

codepen链接:https://codepen.io/t_afif/pen/VwEJqKV

CSS 3D 视差

通常,我们认为“视差效果(parallax)”是一种用于在滚动过程中以不同速度改变元素位置的有趣效果。但我们也可以利用它来为图像创建流畅的悬停效果

836de983a538f3c282c51627c142193f.gif

codepen链接:https://codepen.io/t_afif/pen/qBJyXNy

就像我们在上一节制作的闪光效果一样,我们开始时有一个略微倾斜的图像,在悬停时变得平直。但是,与其应用闪光效果不同,我们使用过渡将图像稍微滑动,使其看起来像是焦点随图像一起旋转,增加了立体感。

你可能会认为我们需要叠加两个相同图像的版本才能实现这个效果,但实际上不需要!这个效果只需要一个图像和几行用于“模拟”视差效果的CSS代码。是的,我称这个效果为“模拟”效果,因为它实际上并不是真正的视差实现,而是一种通过组合运动来欺骗大脑的效果!如果你想看到真正的视差效果,这是一个很好的例子。

图像在这里非常重要。为了获得完美的视觉效果,建议选择一个主要元素位于中心,背景是均匀的图像。就这个效果而言,这可能有些局限性,因此它可能并不适用于每个图像。

图像在悬停时旋转并改变视角,就像上一节中的闪光效果一样。然而,这一次,我们沿着 y 轴 ( rotateY()) 而不是所有三个轴 ( rotate3d()) 旋转。

img {
  transform: perspective(400px) rotateY(8deg);
}
img:hover {
  transform: perspective(400px) rotateY(-8deg);
}

我们通过CSS裁剪和平移的结合来完成滑动运动。这是效果中最棘手的部分。以下是一个简化的演示,来说明主要思路:

0b60835a042c6b89b727b3b235fc64e2.gif

codepen链接:https://codepen.io/t_afif/pen/QWJweZP

我们有一个放置在方框中的图像,方框周围有一个绿色边框表示裁剪区域。裁剪区域是一个正方形,图像在右边稍微超出边界。在悬停时,我们将图像向左滑动(使用 transform: translateX()),而裁剪区域保持原位。

如果我们隐藏超出裁剪区域的图像部分(使用 overflow: hidden),并且应用和上一节中相同的旋转,那么我们就得到了我们想要的“模拟”视差效果:

f9996ef0d8cdffdbc09d602daeb4542b.gif

codepen链接:https://codepen.io/t_afif/pen/VwVYoqr

但是,那个演示中使用了一个额外的 <div> 元素来实现效果。我们的挑战是在没有该元素的情况下完成相同的效果。这就是 clip-path 能够发挥作用的地方:

img {
  --f: .1; /* 视差系数(数值越小越好) */

  --_f: calc(100 * var(--f) / (1 + var(--f)));
  width: 250px; /* image size */
  aspect-ratio: calc(1 + var(--f));
  object-fit: cover;
  clip-path: inset(0 var(--_f) 0 0);
  transition: .5s;
}
img:hover {
  clip-path: inset(0 0 0 var(--_f));
  transform: translateX(calc(-1 * var(--_f)))
}

--f 变量控制着这个效果,它描述了图像应该移动的程度。你会注意到我在使用它来计算一个略大于1的宽高比,以创建一个非方形的图像,然后我们通过裁剪来获取一个方形图像。--_f 定义了我们需要从图像中裁剪的部分,以获得1:1的方形比例。

b701714304960a16e5b4061ef7dffd73.png

展示图像处于悬停状态时,clip-path 值如何变化。

clip-path 定义了裁剪区域,我们希望该区域保持固定。这就是为什么我们在悬停时添加了一个平移效果,将图像向与 clip-path 相反的方向移动。

6006d4e547986f22c673120ade1381a4.gif

codepen链接:https://codepen.io/t_afif/pen/PoxwMry

我们将旋转效果加入其中,效果很完美:

img {
  --f: .1; /* 视差系数(数值越小越好) */
  --r: 10px; /* 半径 */

  --_f: calc(100%*var(--f)/(1 + var(--f)));
  --_a: calc(90deg*var(--f));

  width: 250px; /* image size */
  aspect-ratio: calc(1 + var(--f));
  object-fit: cover;
  clip-path: inset(0 var(--_f) 0 0 round var(--r));
  transform: perspective(400px) translateX(0px) rotateY(var(--_a));
  transition: .5s;
}
img:hover {
  clip-path: inset(0 0 0 var(--_f) round var(--r));
  transform: perspective(400px) translateX(calc(-1*var(--_f))) rotateY(calc(-1*var(--_a)));
}

我稍微圆化了裁剪区域的边缘,使效果更加华丽。如果你想知道为什么我没有使用 border-radius 属性,那是因为该属性在裁剪区域上的效果不太好。幸运的是,clip-path 属性接受 round 值来实现类似的圆角效果。

就是这样!我们已经完成了这个图像上的炫酷悬停效果。

a3f5726f4fe7463ef6e09cbfdf6e6eab.gif

codepen链接:https://codepen.io/t_afif/pen/qBJyXNy

你可以调整视差系数和旋转角度,然后选择最适合你自己工作的图像。

CSS 3D 旋转

对于最后一个演示,我们将为图像添加深度,并将其转化为一个3D盒子。

cefb165b371ea549d323da3bb6053f0a.gif

codepen链接:https://codepen.io/t_afif/pen/yLRRBKj

对于这个效果,我将跳过旋转部分,因为它与我们刚刚在上一个示例中创建的相同。我们将专注于使用 outlineclip-path 属性来实现3D效果。下图说明了它们如何结合在一起形成一个3D盒子。

6014c9351eace951620f916b62584e4e.png

一个轮廓围绕的图像 (1),然后将轮廓偏移以覆盖整个图像 (2),以便可以将其裁剪成一个盒子的形状(3) 。

这是它的工作原理。首先,我们在图像的顶部和底部添加了一些padding,并应用了一个半透明黑色的outline

其次,我们应用了负的 outline-offset,这样轮廓就会覆盖图像的左侧和右侧,而顶部和底部保持不变:

img {
  --d: 18px;  /* 深度 */

  padding-block: var(--d);
  outline: var(--d) solid #0008;
  outline-offset: calc(-1 * var(--d));
}

请注意,我创建了一个变量 --d,用于控制轮廓的厚度。这是赋予图像深度的关键。

最后一步是添加 clip-path。我们需要一个具有八个点的多边形来实现。

7a37191e25fb9face5317b9ac332eeb8.png

显示剪切多边形形状的八个点。

红色的点是固定的,绿色的点是我们将通过动画来展现深度的点。我知道这离一个3D盒子还有些距离,但接下来的视觉效果,当我们添加旋转时,会给出更好的说明。

74b28ad2f895a1e0cbfaa2b1416941ea.png

图像初始时的深度朝一个方向(左侧),然后在悬停时旋转以隐藏深度,使其呈现平面外观(中间)。我们还可以改变深度的方向(右侧)。

初始时,图像带有一定的旋转角度和透视效果。右侧的绿色点与红色点对齐。因此,我们隐藏右侧的轮廓,使其仅在左侧可见。这样我们就得到了深度在左侧的3D盒子。

在悬停时,我们将左侧的绿色点移动,并旋转图像。在动画进行到一半时,所有的绿色点与红色点对齐,旋转角度为0deg,隐藏了轮廓,使图像呈现平面外观。

然后,我们继续旋转,右侧的绿色点移动,而左侧的点保持不变。我们得到了相同的3D效果,但深度在右侧。

请先容忍我,接下来的代码块可能一开始看起来非常混乱。这是因为引入了一些新的变量和我们在 clip-path 属性上绘制的八个点的多边形。

@property --_l {
  syntax: "<flength>";
  initial-value: 0px;
  inherits: true;
}
@property --_r {
  syntax: "<length>";
  initial-value: 0px;
  inherits: true;
}

img {
  --d: 18px;  /* depth */
  --a: 20deg; /* angle */
  --x: 10px;

  --_d: calc(100% - var(--d));
  --_l: 0px;
  --_r: 0px;

  clip-path: polygon(
    /* 左侧的两个绿点 */
    var(--_l) calc(var(--_d) - var(--x)),
    var(--_l) calc(var(--d)  + var(--x)),

    /* 顶部的两个红点 */
    var(--d) var(--d),var(--_d) var(--d),

    /* 右侧的两个绿点 */
    calc(var(--_d) + var(--_r)) calc(var(--d)  + var(--x)),
    calc(var(--_d) + var(--_r)) calc(var(--_d) - var(--x)),

    /* 底部的两个红点 */
    var(--_d) var(--_d),var(--d) var(--_d)

    );
  transition: transform .3s, --_r .15s, --_l .15s .15s;
}

/* 在悬停时更新多边形的点的位置 */
img:hover{
  --_l: var(--d);
  --_r: var(--d);
  --_i: -1;
  transition-delay: 0s, .15s, 0s;
}

我使用了注释来帮助解释代码的作用。请注意,我使用变量 --_l--_r 来定义绿色点的位置。我将这些变量从0动画渐变到深度(--d)的值。在顶部的 @property 声明中,我们可以指定变量的值类型(<length>),以便对其进行动画处理。

注意目前并非所有浏览器都支持 @property。因此,我在演示中添加了一个备用方案,使用了稍微不同的动画效果。

在将多边形绘制在 clip-path 属性上之后,代码接下来应用了一个处理旋转的过渡效果(transition)。完整的旋转持续时间为 0.3 秒,所以绿色点需要在一半的持续时间(0.15 秒)内进行过渡。在悬停状态下,多边形左侧的点立即移动(0 秒),而右侧的点在一半的持续时间后移动(通过一个0.15秒的延迟实现)。当我们离开悬停状态时,我们使用不同的延迟,因为我们需要右侧的点立即移动(0 秒),而左侧的点在一半的持续时间后移动。

那个 --x 变量是什么意思?如果你查看我提供的第一张用来说明 clip-path 点的图像,你会注意到绿色点与顶部和底部边缘有轻微的偏移,这对模拟3D效果是合理的。--x 变量控制了偏移量的大小,但是背后的数学计算比较复杂,不容易用CSS来表达。因此,我们根据每种情况手动更新它,直到找到一个合适的值。

这就是我们的最终结果!

965b956274521bf8b04566dca1b2ef2d.gif

codepen链接:https://codepen.io/t_afif/pen/yLRRBKj

总结

我希望你喜欢这种对 CSS 3D 图像效果的探索,可能在这次探索中受到了一些挑战。我们使用了许多高级的CSS特性,包括蒙版、裁剪、渐变、过渡和计算,为图像创建了一些非常令人惊叹的悬停效果,这些效果通常不会经常见到。

而且我们只需要一行HTML代码就完成了这些效果。没有<div>元素,没有类名或ID,没有伪元素,只需要一个 <img> 标签就足够了。是的,更多的标记可能会使CSS更简单,但它依赖于一个简单的HTML元素意味着CSS可以更广泛地使用。CSS足够强大,可以在一个元素上完成所有这些效果!

- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

69c5b5318768fa512c49dff6052b32ad.png

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值