纯CSS实现Soul星球

4,870 阅读3分钟

起因

最近看到掘金上有朋友用three.js实现了一个soul星球效果,觉得很有意思,想用CSS复刻一下!

01.gif

前置概念!🤠

首先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 轴位置)。

关于三维坐标的概念借用网上一张图

02.png

x依然是水平方向,y依然是垂直方向,z轴则是前后关系!

正文开始!🚀

之前在纯CSS 使用原生三角函数完成时钟效果中已经介绍过现代CSS已经支持了各种三角函数,而我们想要让元素在一个球体上均匀分布也是离不开三角函数!

球面坐标系(Spherical Coordinate System)

在球面坐标系中,一个点的位置可以由以下三个变量描述:

  • 𝜙 (phi):垂直角度,描述点的纬度(从顶部向下的夹角)。
  • 𝜃 (theta):水平角度,描述点的经度(围绕中心的旋转角度)。
  • 𝑟 (radius):半径,点到球心的距离。

03.png

具体的点计算公式为:

  • x=r⋅sin(𝜙)⋅cos(𝜃)
  • 𝑦=𝑟⋅sin(𝜙)⋅sin(𝜃)
  • 𝑧=𝑟⋅cos(𝜙)

根据球面坐标公式我们知道如果需要知道一个点的具体坐标我们需要知道𝜙 (phi)𝜃 (theta)以及半径𝑟 (radius)

金球法则(Golden Section Spiral)

为了让元素均匀分布,𝜙 和 𝜃的计算需要结合元素的数量。可以使用 "金球法则"(Golden Section Spiral)!

金球法则公式的核心思想是:

  • 根据总元素数𝑛在垂直角度𝜙上均匀划分点的位置。
  • 在每个𝜙点上,使用水平角度𝜃随着纬度变化,保持旋转间距均匀。

计算phi的公式

ϕ=arccos(1+n2i)\phi = \arccos\left(-1 + \frac{n}{2 \cdot i}\right)

其中:

  • i:当前点的索引,从 0 到 𝑛−1。
  • n:总点数。
  • 1+n2i-1 + \frac{n}{2 \cdot i} 将点均匀分布在 [−1,1] 的范围内,对应球体垂直方向(从顶部到底部)。

知道了phi之后计算theta的公式如下

θ=nπϕ\theta = \sqrt{n \cdot \pi} \cdot \phi

回到代码中!

现在我们可以根据上述的公式套入代码中~


 --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);

 ...
 ...

04.png

可以看到已经形成了一个比较完美的球形,接下来我们把3D加上!


.circle {
 width: 400px;
 height: 400px;
 perspective: 800px;
 transform-style: preserve-3d;
 position: relative;
}

.wrapper {
 position: relative;
 transform-style: inherit;
 width: 400px;
 height: 400px;
}


05.png

此时已经有了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%;
}

06.png

可以发现确实有部分小球被挡住了~我们利用这个技巧把背景色再改为黑色!

07.png

完整效果展示

css 可以很方便添加一些交互事件,比如:hover以及:input等~

参考资料

WebGL实现soul星球效果

CSS Only 3D Tagcloud V2

三维空间中的球坐标系