本文译者为 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 的强大功能。准备好了解它们的工作原理吧,我们将通过使用CSS的功能来为图像添加透视、深度、旋转,甚至光滑的光泽效果,这些效果可以在你的下一个项目中使用。
我们都认为 3D 效果很酷,对吧?我也这么认为,尤其是当它们与微妙的动画结合在一起时。在本文中,我们将探索一些 CSS 技巧来创建令人惊叹的 3D 效果!
“为什么我们还需要一篇关于 CSS 3D 效果的文章……不是已经有很多了吗?” 是的,但这篇文章有点特别,因为我们将使用尽可能少的 HTML。实际上,这是我们用来为图像制作一些非常惊人的 CSS 效果的唯一标记:
就是这样!我们只需要一个 标签。其他的一切都将在CSS中完成。
下面是它的工作原理。我们将探索三种不同的效果,它们彼此之间没有联系,但可能会互相借鉴一些。你不需要一口气读完整篇文章。实际上,我建议一次阅读一个部分,花时间理解概念和底层代码的作用,然后再继续查看另一种效果。
CSS 3D 闪光
CSS 3D 视差
CSS 3D 旋转
对于第一个效果,我们将在图像上添加一种闪光的动画,并在鼠标悬停时稍微旋转。
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()
完成繁重的计算工作。
这很有趣!但当我们开始处理光泽效果时,它变得更加有趣。一个直观的方法是在图像上方放置一个覆盖层来制作光泽效果。但请记住,我们只使用一个单独的 元素,添加覆盖层需要更多的标记。
你可能会想到使用伪元素。但很遗憾,在这里伪元素不适用于 标签。
我们要做的是使用CSS遮罩和动画渐变来“模拟”闪光效果。我说“模拟”是因为,实际上,你看到的图像是部分透明的。在悬停时,透明度会更新以创建出闪亮的效果。
我知道这并不容易理解,但如果你考虑到我们的背景是黑色的 ,是的,这就是技巧的一部分!使图像部分透明类似于使图像变暗。当图像被悬停时,我们调整透明度使其变亮。
以下是一个使用opacity
来更好理解我所说的内容的简化示例:
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%)
.
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; /* 在悬停时移动到左上角 */
}
来看一下吧!我们在悬停时有一个漂亮的闪光效果!
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代码,我们就可以实现这个效果。下面是一个图示,用来说明遮罩中使用的不同数值:
*演示 CSS 遮罩如何覆盖图像以及图像如何在悬停时滑动。*演示 CSS 遮罩如何覆盖图像以及图像如何在悬停时滑动。
绿色框表示了渐变区域,蓝色线条定义了我们使用的颜色位置。初始状态下,渐变框被放置在100% 100%
的位置,悬停时,我们将其滑动到0 0
的位置。滑动效果将沿着图像移动渐变的对角部分(不透明部分),从而创建闪光效果。
这是完整的演示,我甚至为你提供了第二个变体样式,供你详细研究并了解其工作原理。
codepen链接:https://codepen.io/t_afif/pen/VwEJqKV
通常,我们认为“视差效果(parallax)”是一种用于在滚动过程中以不同速度改变元素位置的有趣效果。但我们也可以利用它来为图像创建流畅的悬停效果。
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裁剪和平移的结合来完成滑动运动。这是效果中最棘手的部分。以下是一个简化的演示,来说明主要思路:
codepen链接:https://codepen.io/t_afif/pen/QWJweZP
我们有一个放置在方框中的图像,方框周围有一个绿色边框表示裁剪区域。裁剪区域是一个正方形,图像在右边稍微超出边界。在悬停时,我们将图像向左滑动(使用 transform: translateX()
),而裁剪区域保持原位。
如果我们隐藏超出裁剪区域的图像部分(使用 overflow: hidden
),并且应用和上一节中相同的旋转,那么我们就得到了我们想要的“模拟”视差效果:
codepen链接:https://codepen.io/t_afif/pen/VwVYoqr
但是,那个演示中使用了一个额外的 展示图像处于悬停状态时, codepen链接:https://codepen.io/t_afif/pen/PoxwMry 我们将旋转效果加入其中,效果很完美: 我稍微圆化了裁剪区域的边缘,使效果更加华丽。如果你想知道为什么我没有使用 就是这样!我们已经完成了这个图像上的炫酷悬停效果。 codepen链接:https://codepen.io/t_afif/pen/qBJyXNy 你可以调整视差系数和旋转角度,然后选择最适合你自己工作的图像。 对于最后一个演示,我们将为图像添加深度,并将其转化为一个3D盒子。 codepen链接:https://codepen.io/t_afif/pen/yLRRBKj 对于这个效果,我将跳过旋转部分,因为它与我们刚刚在上一个示例中创建的相同。我们将专注于使用 一个轮廓围绕的图像 (1),然后将轮廓偏移以覆盖整个图像 (2),以便可以将其裁剪成一个盒子的形状(3) 。 这是它的工作原理。首先,我们在图像的顶部和底部添加了一些padding,并应用了一个半透明黑色的 其次,我们应用了负的 请注意,我创建了一个变量 最后一步是添加 显示剪切多边形形状的八个点。 红色的点是固定的,绿色的点是我们将通过动画来展现深度的点。我知道这离一个3D盒子还有些距离,但接下来的视觉效果,当我们添加旋转时,会给出更好的说明。 图像初始时的深度朝一个方向(左侧),然后在悬停时旋转以隐藏深度,使其呈现平面外观(中间)。我们还可以改变深度的方向(右侧)。 初始时,图像带有一定的旋转角度和透视效果。右侧的绿色点与红色点对齐。因此,我们隐藏右侧的轮廓,使其仅在左侧可见。这样我们就得到了深度在左侧的3D盒子。 在悬停时,我们将左侧的绿色点移动,并旋转图像。在动画进行到一半时,所有的绿色点与红色点对齐,旋转角度为0deg,隐藏了轮廓,使图像呈现平面外观。 然后,我们继续旋转,右侧的绿色点移动,而左侧的点保持不变。我们得到了相同的3D效果,但深度在右侧。 请先容忍我,接下来的代码块可能一开始看起来非常混乱。这是因为引入了一些新的变量和我们在 我使用了注释来帮助解释代码的作用。请注意,我使用变量 注意:目前并非所有浏览器都支持 在将多边形绘制在 那个 这就是我们的最终结果! codepen链接:https://codepen.io/t_afif/pen/yLRRBKj 我希望你喜欢这种对 CSS 3D 图像效果的探索,可能在这次探索中受到了一些挑战。我们使用了许多高级的CSS特性,包括蒙版、裁剪、渐变、过渡和计算,为图像创建了一些非常令人惊叹的悬停效果,这些效果通常不会经常见到。 而且我们只需要一行HTML代码就完成了这些效果。没有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
的方形比例。clip-path
值如何变化。clip-path
定义了裁剪区域,我们希望该区域保持固定。这就是为什么我们在悬停时添加了一个平移效果,将图像向与 clip-path
相反的方向移动。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
值来实现类似的圆角效果。CSS 3D 旋转
outline
和 clip-path
属性来实现3D效果。下图说明了它们如何结合在一起形成一个3D盒子。outline
。outline-offset
,这样轮廓就会覆盖图像的左侧和右侧,而顶部和底部保持不变:img {
--d: 18px; /* 深度 */
padding-block: var(--d);
outline: var(--d) solid #0008;
outline-offset: calc(-1 * var(--d));
}
--d
,用于控制轮廓的厚度。这是赋予图像深度的关键。clip-path
。我们需要一个具有八个点的多边形来实现。clip-path
属性上绘制的八个点的多边形。@property --_l {
syntax: "
--_l
和 --_r
来定义绿色点的位置。我将这些变量从0动画渐变到深度(--d
)的值。在顶部的 @property
声明中,我们可以指定变量的值类型(
),以便对其进行动画处理。@property
。因此,我在演示中添加了一个备用方案,使用了稍微不同的动画效果。clip-path
属性上之后,代码接下来应用了一个处理旋转的过渡效果(transition
)。完整的旋转持续时间为 0.3 秒
,所以绿色点需要在一半的持续时间(0.15 秒
)内进行过渡。在悬停状态下,多边形左侧的点立即移动(0 秒
),而右侧的点在一半的持续时间后移动(通过一个0.15
秒的延迟实现)。当我们离开悬停状态时,我们使用不同的延迟,因为我们需要右侧的点立即移动(0 秒
),而左侧的点在一半的持续时间后移动。--x
变量是什么意思?如果你查看我提供的第一张用来说明 clip-path
点的图像,你会注意到绿色点与顶部和底部边缘有轻微的偏移,这对模拟3D效果是合理的。--x
变量控制了偏移量的大小,但是背后的数学计算比较复杂,不容易用CSS来表达。因此,我们根据每种情况手动更新它,直到找到一个合适的值。总结