起因
最近看到掘金上有朋友用three.js
实现了一个soul星球效果,觉得很有意思,想用CSS
复刻一下!
前置概念!🤠
首先CSS
默认在二维平面 (x, y) 上操作:
- x 轴:对应于水平方向。
- y 轴:对应于垂直方向。
- 常见的属性包括:
-
位置相关:left, right, top, bottom
-
尺寸相关:width, height
-
变换相关:transform: translateX(), translateY()
-
通过 CSS
的 3D 变换,可以操作 z 轴,形成三维效果:
- z 轴:垂直于屏幕的方向(正值向外,负值向内)。
- 相关属性包括:
- 3D 变换属性:
- transform: translateZ()
- transform: rotateX(), rotateY(), rotateZ()
- transform: scaleZ()
- perspective:设置观察视角的透视效果。
- transform-origin:定义变换的原点(可以包括 z 轴位置)。
- 3D 变换属性:
关于三维坐标的概念借用网上一张图
x
依然是水平方向,y
依然是垂直方向,z
轴则是前后关系!
正文开始!🚀
之前在纯CSS 使用原生三角函数完成时钟效果中已经介绍过现代CSS
已经支持了各种三角函数,而我们想要让元素在一个球体上均匀分布也是离不开三角函数!
球面坐标系(Spherical Coordinate System)
在球面坐标系中,一个点的位置可以由以下三个变量描述:
- 𝜙 (phi):垂直角度,描述点的纬度(从顶部向下的夹角)。
- 𝜃 (theta):水平角度,描述点的经度(围绕中心的旋转角度)。
- 𝑟 (radius):半径,点到球心的距离。
具体的点计算公式为:
- x=r⋅sin(𝜙)⋅cos(𝜃)
- 𝑦=𝑟⋅sin(𝜙)⋅sin(𝜃)
- 𝑧=𝑟⋅cos(𝜙)
根据球面坐标公式我们知道如果需要知道一个点的具体坐标我们需要知道𝜙 (phi)
和𝜃 (theta)
以及半径𝑟 (radius)
金球法则(Golden Section Spiral)
为了让元素均匀分布,𝜙 和 𝜃的计算需要结合元素的数量。可以使用 "金球法则"(Golden Section Spiral)!
金球法则公式的核心思想是:
- 根据总元素数𝑛在垂直角度𝜙上均匀划分点的位置。
- 在每个𝜙点上,使用水平角度𝜃随着纬度变化,保持旋转间距均匀。
计算phi
的公式
其中:
- i:当前点的索引,从 0 到 𝑛−1。
- n:总点数。
- 将点均匀分布在 [−1,1] 的范围内,对应球体垂直方向(从顶部到底部)。
知道了phi
之后计算theta
的公式如下
回到代码中!
现在我们可以根据上述的公式套入代码中~
--index: 1;
--radius: 195;
--count: 100;
--_phi: acos(calc(-1 + (2 * var(--index)) / var(--count)));
--_theta: calc(sqrt(calc(var(--count) * 3.141592653589793)) * var(--_phi));
--_x: calc(cos(var(--_theta)) * sin(var(--_phi)));
--_y: calc(sin(var(--_theta)) * sin(var(--_phi)));
--_z: calc(cos(var(--_phi)));
--_scaled-x: calc(var(--_x) * var(--radius));
--_scaled-y: calc(var(--_y) * var(--radius));
--_scaled-z: calc(var(--_z) * var(--radius));
--_final-x: calc(var(--_scaled-x) + var(--radius));
--_final-y: calc(var(--_scaled-y) + var(--radius));
--_final-z: var(--_scaled-z);
...
...
可以看到已经形成了一个比较完美的球形,接下来我们把3D
加上!
.circle {
width: 400px;
height: 400px;
perspective: 800px;
transform-style: preserve-3d;
position: relative;
}
.wrapper {
position: relative;
transform-style: inherit;
width: 400px;
height: 400px;
}
此时已经有了3D
的景深感了!我们给.wrapper
加上旋转效果看看!注意❗外部容器旋转我们的子元素应该反向旋转,这样可以保证永远有一面正对用户~
旋转效果已经实现了,接下来我们加上颜色和文字来看看效果~
// 利用scss产生一系列颜色
@for $i from 1 through 100 {
$random-hue: random(360);
$random-saturation: random(40) + 10;
$random-lightness: random(40) + 40;
.point-#{$i} {
& > div {
position: relative;
display: flex;
flex-direction: column;
gap: 2px;
align-items: center;
justify-content: center;
cursor: pointer;
}
& > div:before {
content: 'content';
color: white;
font-size: 12px;
}
& > div:after {
content: '';
width: 10px;
height: 10px;
border-radius: 50%;
background: hsl($random-hue, $random-saturation, $random-lightness);
}
}
}
效果还是不错的,但是就是还差了一点感觉,就是小球旋转到后面的时候应该产生一种透明度的变化使后面的元素有一种看不清的感觉!这时就需要我们使用一个小技巧~
现在我们的小球元素已经有了z
轴的概念,区分前后,而我们的.circle
依然是平面,所以如果我们给.circle
设置了一个背景,小球元素就会有部分被挡在后面!
.circle {
...
...
background: radial-gradient(rgba(0, 0, 0, 0.75) 15%, rgba(0, 0, 0, 0) calc(75% - 30px));
border-radius: 50%;
}
可以发现确实有部分小球被挡住了~我们利用这个技巧把背景色再改为黑色!
完整效果展示
css
可以很方便添加一些交互事件,比如:hover
以及:input
等~