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

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

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

原文链接:www.smashingmagazine.com/2023/07/shi...

原文作者: Temani Afif

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

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

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

"为什么我们还需要一篇关于 CSS 3D 效果的文章......不是已经有很多了吗?"

是的,但这篇文章有点特别,因为我们将使用尽可能少的 HTML。实际上,这是我们用来为图像制作一些非常惊人的 CSS 效果的唯一标记:

ini 复制代码
<img src="" alt="">

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

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

目录

  • CSS 3D 闪光

  • CSS 3D 视差

  • CSS 3D 旋转

CSS 3D 闪光

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

codepen链接:codepen.io/t\_afif/pen...

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

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

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即可。

css 复制代码
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来更好理解我所说的内容的简化示例:

codepen链接:codepen.io/t\_afif/pen...

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

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

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

codepen链接:codepen.io/t\_afif/pen...

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

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

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

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

codepen链接:codepen.io/t\_afif/pen...

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

css 复制代码
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代码,我们就可以实现这个效果。下面是一个图示,用来说明遮罩中使用的不同数值:

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

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

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

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

codepen链接:codepen.io/t\_afif/pen...

CSS 3D 视差

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

codepen链接:codepen.io/t\_afif/pen...

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

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

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

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

css 复制代码
img {  transform: perspective(400px) rotateY(8deg);}img:hover {  transform: perspective(400px) rotateY(-8deg);}

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

codepen链接:codepen.io/t\_afif/pen...

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

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

codepen链接:codepen.io/t\_afif/pen...

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

css 复制代码
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的方形比例。

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

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

codepen链接:codepen.io/t\_afif/pen...

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

css 复制代码
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 值来实现类似的圆角效果。

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

codepen链接:codepen.io/t\_afif/pen...

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

CSS 3D 旋转

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

codepen链接:codepen.io/t\_afif/pen...

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

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

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

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

css 复制代码
img {  --d: 18px;  /* 深度 */  padding-block: var(--d);  outline: var(--d) solid #0008;  outline-offset: calc(-1 * var(--d));}

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

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

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

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

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

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

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

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

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

css 复制代码
@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来表达。因此,我们根据每种情况手动更新它,直到找到一个合适的值。

这就是我们的最终结果!

codepen链接:codepen.io/t\_afif/pen...

总结

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

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

相关推荐
web182854825124 分钟前
ctfshow-web 151-170-文件上传
前端·状态模式
轻口味9 分钟前
【每日学点鸿蒙知识】Web请求支持Http、PDF展示、APP上架应用搜索问题、APP备案不通过问题、滚动列表问题
前端·http·harmonyos
一棵开花的树,枝芽无限靠近你18 分钟前
【PPTist】表格功能
前端·笔记·学习·编辑器·ppt·pptist
马船长1 小时前
RCE-PLUS (学习记录)
java·linux·前端
学前端的小朱1 小时前
修改输出资源的名称和路径、自动清空上次打包资源
前端·webpack·打包工具
嘤嘤怪呆呆狗1 小时前
【开发问题记录】执行 git cz 报require() of ES Module…… 错误
前端·javascript·vue.js·git·vue
夜斗(dou)2 小时前
谷歌开发者工具 - 网络篇
前端·网络·chrome devtools
常常不爱学习2 小时前
CSS盒子模型(溢出隐藏,块级元素和行级元素的居中对齐,元素样式重置)
前端·css
风抽过的烟头2 小时前
Python提取字符串中的json,时间,特定字符
前端·python·json
SomeB1oody2 小时前
【Rust自学】6.3. 控制流运算符-match
开发语言·前端·rust