重新认识,用css画一个3D奥运五环原理 我们将使用图层来创建 3D 效果。这些图层一个接一个地放置(在 z 轴上),以
巴黎奥运虽然刚过,但是学习知识不能停止,今天我们用css来画一个3D效果的奥运五环吧,顺便增长下自己的css技能。
原理
我们将使用图层来创建 3D 效果。这些图层一个接一个地放置(在 z 轴上),以获得 3D 对象的深度,在我们的例子中,该对象是一个环。每个图层的形状、大小和颜色的组合(以及它们在图层之间的变化方式)创建了完整的 3D 对象。
在这种情况下,我使用了 16 个层,每个层都是不同的色调(较暗的层堆叠在后面)以获得简单的照明效果,并使用每个层的大小和厚度来建立圆形。
就 HTML 而言,我们需要五个
html
这是 HTML 的缩写版本,展示了其如何组合在一起:
<div class="rings">
<div class="ring">
<i style="--i: 1;"></i>
<i style="--i: 2;"></i>
<i style="--i: 3;"></i>
<i style="--i: 4;"></i>
<i style="--i: 5;"></i>
<i style="--i: 6;"></i>
<i style="--i: 7;"></i>
<i style="--i: 8;"></i>
<i style="--i: 9;"></i>
<i style="--i: 10;"></i>
<i style="--i: 11;"></i>
<i style="--i: 12;"></i>
<i style="--i: 13;"></i>
<i style="--i: 14;"></i>
<i style="--i: 15;"></i>
<i style="--i: 16;"></i>
</div>
<!-- 4 more rings... -->
</div>
请注意我在每个元素的属性--i
上放置的自定义属性:style<i>
我们将使用 来--i
计算每个图层的位置、大小和颜色。这就是为什么我将它们的值设置为按升序排列的整数——这些将是用于单独排列和设置每个图层样式的乘数。
css
让我们从父.rings
容器开始,现在它只会获得一个相对位置。如果没有相对定位,当设置绝对定位时,环将从文档流中移除,并最终离开页面的某个地方。
.rings { position: relative; }
.ring { position: absolute; }
让我们对<i>
元素进行同样的操作,但使用CSS 嵌套来保持代码紧凑。我们会在border-radius
操作过程中修剪方形边缘以形成完美的圆形。
.rings {
position: relative;
}
.ring {
position: absolute;
i {
position: absolute;
border-radius: 50%;
}
}
在继续之前,我们将应用的最后一项基本样式是 的自定义属性--ringColor
。这将使环的着色变得相当简单,因为我们可以编写一次,然后逐层覆盖它。我们--ringColor
在border
属性上声明,因为我们只希望在每层的外边缘着色,而不是用 完全填充它们background-color
:
.rings {
position: relative;
}
.ring {
position: absolute;
--ringColor: #0085c7;
i {
position: absolute;
inset: -100px;
border: 16px var(--ringColor) solid;
border-radius: 50%;
}
}
你有没有注意到我偷偷地把其他东西放进去了?没错,属性inset
也在那里,并设置为负值100px
。这可能看起来有点奇怪,所以我们先讨论一下这个问题,然后再继续设计我们的作品。
将属性设置为负值inset
意味着图层的位置超出了元素的范围.ring
。因此,我们可能更愿意将其视为“起始”。在我们的例子中,.ring
没有大小,因为没有内容或 CSS 属性来为其赋予尺寸。这意味着图层inset
(或者更确切地说是“起始”)100px
在每个方向上,从而导致 为.ring
200×200 像素。
设置3D深度
我们利用图层来营造深度感。我们通过将 16 个图层沿 z 轴放置来实现这一点,z 轴将元素从前到后堆叠。我们将每个图层之间留出一点2px
距离 — 这就是我们在每层之间创建轻微视觉分离所需的所有空间,从而实现我们想要的深度。
还记得--i
我们在 HTML 中使用的自定义属性吗?
<i style="--i: 1;"></i>
<i style="--i: 2;"></i>
<i style="--i: 3;"></i>
再次强调,这些是乘数,用于帮助我们translate
沿 z 轴计算每一层。让我们创建一个新的自定义属性来定义方程,以便我们可以将其应用于每一层:
i { --translateZ: calc(var(--i) * 2px); }
我们将其应用于什么?我们可以使用 CSStransform
属性。这样,我们可以垂直旋转图层(即rotateY()
),同时沿 z 轴平移它们:
i {
--translateZ: calc(var(--i) * 2px);
transform: rotateY(-45deg) translateZ(var(--translateZ));
}
阴影颜色
对于颜色阴影,我们将根据图层的位置使其变暗,这样当我们从 z 轴的前面移动到后面时,图层会变暗。有几种方法可以做到这一点。一种方法是放入另一个黑色图层,并降低不透明度。另一种方法是修改hsl()
颜色函数中的“亮度”通道,其中值在前面“较亮”,向后逐渐变暗。第三个选项是调整图层的不透明度,但这会变得很混乱。
尽管我们有这三种方法,但我认为现代 CSS 相对颜色语法是最好的方法。我们已经定义了一个默认的--ringColor
自定义属性。我们可以将它放入相对颜色语法中,将其操纵为每个环层的其他颜色<i>
。
首先,我们需要一个新的自定义属性来计算“light”值:
.ring {
--ringColor: #0085c7;
i {
--light: calc(var(--i) / 16);
border: 16px var(--ringColor) solid;
}
}
我们将calc()
在另一个自定义属性中使用 -ulated 结果,该属性将我们的默认值--ringColor
置于相对颜色语法中,其中--light
自定义属性有助于修改结果颜色的亮度。
.ring {
--ringColor: #0085c7;
i {
--light: calc(var(--i) / 16);
--layerColor: rgb(from var(--ringColor) calc(r * var(--light)) calc(g * var(--light)) calc(b * var(--light)));
border: 16px var(--ringColor) solid;
}
}
这真是一个相当复杂的方程!但它看起来很复杂,因为相对颜色语法需要颜色(RGB)中每个通道的参数,而我们正在计算每个参数。
rgb(from origin-color channelR channelG channelB)
至于计算,我们将每个 RGB 通道乘以自定义属性,该属性是介于和--light
之间的数字,乘以层数。
创建形状
为了获得圆环形状,我们将使用属性设置图层的大小(即厚度)border
。这是我们可以在工作中开始使用三角函数的地方!
我们希望每个环的厚度介于 到 之间 0deg
—180deg
因为实际上我们只制作了半个圆 — 因此我们将除以180deg
层数,16
,结果为11.25deg
。使用sin()
三角函数(相当于直角的对边和斜边),我们得到层的的表达式--size
:
--size: calc(sin(var(--i) * 11.25deg) * 16px);
因此,无论--i
HTML 中的内容是什么,它都充当计算图层border
厚度的乘数。我们一直像这样声明图层的边框:
i {
border: 16px var(--ringColor) solid;
}
16px
现在我们可以用计算来替换硬编码值--size
:
i {
--size: calc(sin(var(--i) * 11.25deg) * 16px);
border: var(--size) var(--layerColor) solid;
}
但是!您可能已经注意到,当我们改变图层的border
宽度时,我们并没有改变图层的大小。因此,圆形轮廓仅出现在图层的内侧。这里的关键是理解使用属性设置,--size
这inset
意味着它不会影响元素的box-sizing
。结果肯定是一个 3D 环,但大部分阴影都被埋没了。.
我们可以通过为每一层计算一个新的来呈现阴影inset
。这有点像我在 2020 版本中所做的,但我认为我找到了一种更简单的方法:添加具有outline
相同border
值的 来完成环外侧的圆弧。现在我们已经建立了一个看起来更自然的环outline
:
i {
--size: calc(sin(var(--i) * 11.25deg) * 16px);
border: var(--size) var(--layerColor) solid;
outline: var(--size) var(--layerColor) solid;
}
动画戒指
在最后一个演示中,我必须为戒指添加动画,以比较戒指前后的阴影。我们将在最后一个演示中使用相同的动画,因此在将其他四个戒指添加到 HTML 之前,让我们分解一下我是如何做到这一点的
我并没有想做什么花哨的事情;我只是将 y 轴的旋转设置为从-45deg
到45deg
(translateZ
值保持不变)。
@keyframes ring {
from { transform: rotateY(-45deg) translateZ(var(--translateZ, 0)); }
to { transform: rotateY(45deg) translateZ(var(--translateZ, 0)); }
}
至于animation
属性,我将其命名为ring
,并硬编码(至少目前)一个无限循环的持续时间。分别使用和3s
来设置动画的计时函数,可实现平滑的来回运动。ease-in-out``alternate
i {
animation: ring 3s infinite ease-in-out alternate;
}
添加更多戒指
现在我们可以将剩余的四个环添加到 HTML 中。请记住,我们总共有五个环,每个环包含 16 个<i>
层。它看起来可能很简单:
<div class="rings">
<div class="ring"> <!-- <i> layers --> </div>
<div class="ring"> <!-- <i> layers --> </div>
<div class="ring"> <!-- <i> layers --> </div>
<div class="ring"> <!-- <i> layers --> </div>
<div class="ring"> <!-- <i> layers --> </div>
</div>
这种标记的简洁性非常优雅。我们可以使用 CSSnth-child()
伪选择器单独选择它们。我喜欢更声明一点,我将为每个标记添加.ring
额外的类,以便我们能够明确选择给定的环。
<div class="rings">
<div class="ring ring__1"> <!-- layers --> </div>
<div class="ring ring__2"> <!-- layers --> </div>
<div class="ring ring__3"> <!-- layers --> </div>
<div class="ring ring__4"> <!-- layers --> </div>
<div class="ring ring__5"> <!-- layers --> </div>
</div>
我们现在的任务是单独调整每个戒指。现在,一切看起来都像我们一起制作的第一个戒指。我们将使用刚刚在 HTML 中设置的唯一类来为它们赋予自己的颜色、位置和动画持续时间。
好消息?我们一直在使用自定义属性!我们所要做的就是更新每个环的唯一类中的值。
.ring {
&.ring__1 { --ringColor: #0081c8; --duration: 3.2s; --translate: -240px, -40px; }
&.ring__2 { --ringColor: #fcb131; --duration: 2.6s; --translate: -120px, 40px; }
&.ring__3 { --ringColor: #444444; --duration: 3.0s; --translate: 0, -40px; }
&.ring__4 { --ringColor: #00a651; --duration: 3.4s; --translate: 120px, 40px; }
&.ring__5 { --ringColor: #ee334e; --duration: 2.8s; --translate: 240px, -40px; }
}
如果你想知道这些--ringColor
值从何而来,我以国际奥林匹克委员会记录的颜色为基础
。每个颜色--duration
彼此略微偏移,以错开环之间的移动,并且环被--translate
分开,然后通过交替其位置和120px
垂直交错。40px``-40px
让我们将翻译内容应用到.ring
元素中:
.ring { transform: translate(var(--translate)); }
之前,我们将动画的持续时间设置为硬编码的三秒:
i { animation: ring 3s infinite ease-in-out alternate; }
现在是时候用一个自定义属性来代替它了,该属性可以分别计算每个环的持续时间。
i { animation: ring var(--duration) -10s infinite ease-in-out alternate; }
哇哦,哇哦!这个-10s
值在里面干什么?尽管每个环层都设置为以不同的持续时间进行动画,但动画的起始角度都是相同的。在更改持续时间时添加一个恒定的负延迟将确保每个环的动画以不同的角度开始。
最后一些改进
我们到了最后一步!动画看起来非常棒,但我想再添加两件事。第一件是-10deg
父容器 x 轴上的小“倾斜” .rings
。这将使我们看起来像是从更高的角度看事物。
.rings { rotate: x -10deg; }
第二个收尾部分与阴影有关。我们可以真正突出作品的 3D 深度,只需选择.ring
元素的::after
伪元素并将其设置为阴影样式即可。
首先,我们将伪元素的边框和轮廓的宽度设置为常数 ( 24px
),同时将颜色设置为半透明黑色 ( #0003
)。然后我们将translate
它们拉远,使它们看起来更远。我们还将inset
它们与实际环对齐。基本上,我们相对于实际元素移动伪元素。
.ring {
&::after {
content: '';
position: absolute;
inset: -100px;
border: 24px #0003 solid;
outline: 24px #0003 solid;
translate: 0 -100px -400px;
}
}
目前,伪星看起来不太像阴影。但如果我们blur()
稍微观察一下,它们就会像阴影一样:
.ring {
&::after {
content: '';
position: absolute;
inset: -100px;
border: 24px #0003 solid;
outline: 24px #0003 solid;
translate: 0 -100px -400px;
filter: blur(12px);
}
}
阴影也非常方正。让我们确保它们像圆环一样圆润:
.ring {
&::after {
content: '';
position: absolute;
inset: -100px;
border: 24px #0003 solid;
outline: 24px #0003 solid;
translate: 0 -100px -400px;
filter: blur(12px);
border-radius: 50%;
}
}
哦,我们应该在伪动画上设置相同的动画,以便阴影与环协调移动:
.ring {
&::after {
content: '';
position: absolute;
inset: -100px;
border: 24px #0003 solid;
outline: 24px #0003 solid;
translate: 0 -100px -400px;
filter: blur(12px);
border-radius: 50%;
animation: ring var(--duration) -10s infinite ease-in-out alternate;
}
}
结束
- 通过这个案例我们可以发现css是很强大的,可以实现很多不一样的效果 *如图:
- 代码也贡享下:
<div class="rings">
<div class="ring ring__1">
<i style="--i: 1;"></i>
<i style="--i: 2;"></i>
<i style="--i: 3;"></i>
<i style="--i: 4;"></i>
<i style="--i: 5;"></i>
<i style="--i: 6;"></i>
<i style="--i: 7;"></i>
<i style="--i: 8;"></i>
<i style="--i: 9;"></i>
<i style="--i: 10;"></i>
<i style="--i: 11;"></i>
<i style="--i: 12;"></i>
<i style="--i: 13;"></i>
<i style="--i: 14;"></i>
<i style="--i: 15;"></i>
<i style="--i: 16;"></i>
</div>
<div class="ring ring__2">
<i style="--i: 1;"></i>
<i style="--i: 2;"></i>
<i style="--i: 3;"></i>
<i style="--i: 4;"></i>
<i style="--i: 5;"></i>
<i style="--i: 6;"></i>
<i style="--i: 7;"></i>
<i style="--i: 8;"></i>
<i style="--i: 9;"></i>
<i style="--i: 10;"></i>
<i style="--i: 11;"></i>
<i style="--i: 12;"></i>
<i style="--i: 13;"></i>
<i style="--i: 14;"></i>
<i style="--i: 15;"></i>
<i style="--i: 16;"></i>
</div>
<div class="ring ring__3">
<i style="--i: 1;"></i>
<i style="--i: 2;"></i>
<i style="--i: 3;"></i>
<i style="--i: 4;"></i>
<i style="--i: 5;"></i>
<i style="--i: 6;"></i>
<i style="--i: 7;"></i>
<i style="--i: 8;"></i>
<i style="--i: 9;"></i>
<i style="--i: 10;"></i>
<i style="--i: 11;"></i>
<i style="--i: 12;"></i>
<i style="--i: 13;"></i>
<i style="--i: 14;"></i>
<i style="--i: 15;"></i>
<i style="--i: 16;"></i>
</div>
<div class="ring ring__4">
<i style="--i: 1;"></i>
<i style="--i: 2;"></i>
<i style="--i: 3;"></i>
<i style="--i: 4;"></i>
<i style="--i: 5;"></i>
<i style="--i: 6;"></i>
<i style="--i: 7;"></i>
<i style="--i: 8;"></i>
<i style="--i: 9;"></i>
<i style="--i: 10;"></i>
<i style="--i: 11;"></i>
<i style="--i: 12;"></i>
<i style="--i: 13;"></i>
<i style="--i: 14;"></i>
<i style="--i: 15;"></i>
<i style="--i: 16;"></i>
</div>
<div class="ring ring__5">
<i style="--i: 1;"></i>
<i style="--i: 2;"></i>
<i style="--i: 3;"></i>
<i style="--i: 4;"></i>
<i style="--i: 5;"></i>
<i style="--i: 6;"></i>
<i style="--i: 7;"></i>
<i style="--i: 8;"></i>
<i style="--i: 9;"></i>
<i style="--i: 10;"></i>
<i style="--i: 11;"></i>
<i style="--i: 12;"></i>
<i style="--i: 13;"></i>
<i style="--i: 14;"></i>
<i style="--i: 15;"></i>
<i style="--i: 16;"></i>
</div>
</div>
*, *::before, *::after {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
background-image: radial-gradient(circle, #eee, #bbb 500px);
min-height: 100vh;
display: grid;
place-items: center;
perspective: 800px;
* {
transform-style: preserve-3d;
}
}
.rings {
position: relative;
rotate: x -10deg;
}
.ring {
position: absolute;
transform: translate(var(--translate));
&.ring__1 { --ringColor: #0081c8; --duration: 3.2s; --translate: -240px, -40px; }
&.ring__2 { --ringColor: #fcb131; --duration: 2.6s; --translate: -120px, 40px; }
&.ring__3 { --ringColor: #444444; --duration: 3.0s; --translate: 0, -40px; }
&.ring__4 { --ringColor: #00a651; --duration: 3.4s; --translate: 120px, 40px; }
&.ring__5 { --ringColor: #ee334e; --duration: 2.8s; --translate: 240px, -40px; }
i {
--translateZ: calc(var(--i) * 2px);
--light: calc(var(--i) / 16);
--layerColor: rgb(from var(--ringColor) calc(r * var(--light)) calc(g * var(--light)) calc(b * var(--light)));
--size: calc(sin(var(--i) * 11.25deg) * 16px);
position: absolute;
inset: -100px;
border: var(--size) var(--layerColor) solid;
outline: var(--size) var(--layerColor) solid;
border-radius: 50%;
animation: ring var(--duration) -10s infinite ease-in-out alternate;
}
&::after {
content: '';
position: absolute;
inset: -100px;
border: 24px #0003 solid;
outline: 24px #0003 solid;
border-radius: 50%;
translate: 0 -100px -400px;
filter: blur(12px);
animation: ring var(--duration) -10s infinite ease-in-out alternate;
}
}
@keyframes ring {
from { transform: rotateY(-45deg) translateZ(var(--translateZ, 0)); }
to { transform: rotateY(45deg) translateZ(var(--translateZ, 0)); }
}
转载自:https://juejin.cn/post/7410965438044618790