想不到空间站🛰️是这样飞的...

805 阅读3分钟

哈喽,今天用CSS实现围绕「地球🌏」旋转的「空间站🛰️」。

效果

屏幕录制2023-06-25 上午10.13.38.2023-06-25 10_16_47 AM.gif

从图片中可以看出,整体分为3部分:

  1. 黑色背景
  2. 旋转的「地球🌍」
  3. 围绕地球飞行的「空间站🛰️」

实现

空间站

从正对屏幕看,「空间站🛰️」的运动属于椭圆运动,对于CSS而言,实现「椭圆运动」是有一定难度的。

但不妨换个角度看,如果从地球的「北极」上空往下看,「空间站🛰️」就是个普通的圆形旋转运动。

因此,可以把「空间站🛰️」的圆形运动轨迹进行「X轴旋转」,这样从屏幕角度看起来就是一个椭圆。

<template>
<div class="solar-system">
    <div class="box">
      <div class="locus"></div>
    </div>
 </div>
</template>
<style>
.box {
  position: relative;
  width: 520px;
  height: 520px;

  perspective-origin: top;
  perspective: 1000;
  -webkit-perspective-origin: top;
  -webkit-perspective: 1000;

  border: 1px solid black;

  .locus {
    width: 500px;
    height: 500px;

    margin: auto;

    transform: rotateX(90deg);

    border-radius: 50%;
    border: 2px solid red;
  }
}
</style>

可能需要理解的CSS属性:

  1. perspective: 1000: 指定了观察者与z=0平面的距离,使具有三维位置变换的元素产生透视效果;
  2. perspective-origin: top:指定了观察者的位置为top
  3. rotateX(90deg):围绕其X轴以90deg度数进行旋转;

接下来,往locus中添加「空间站🛰️」:

<div class="locus">
    <div class="station">
        <img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6f9ac53fa9684dcc96ec99c57bd0c663~tplv-k3u1fbpfcp-watermark.image" />
    </div>
</div>
<style>
.locus {
    position: relative;
    width: 500px;
    height: 500px;

    margin: auto;

    transform-style: preserve-3d;
    transform: rotateX(90deg);

    border-radius: 50%;
    border: 2px solid red;

    .station {
      position: absolute;
      top: 150px;
      left: calc(50% - 250px);

      width: 250px;
      height: 100px;

      transform-origin: right bottom;
      transform: rotateX(-90deg);

      transform-style: preserve-3d;

      img {
        position: absolute;
        z-index: 3;
        left: 0px;
        bottom: 0px;
        width: 50px;
      }
    }
}
</style>

可能需要理解的CSS属性:

  1. transform-style: preserve-3d:设置元素的子元素是位于 3D 空间中;
  2. transform-origin: right bottom:更改一个元素变形的原点;

注意,要把「空间站🛰️」进行rotateX(-90deg)旋转,这样就能「立起来」,不然的话,「空间站🛰️」会随着locus旋转成椭圆。

一切准备就绪,紧接着把这个圆旋转起来,这样就能带动「空间站🛰️」动起来了。

可以利用rotateZ,从0deg变换到360deg,就能实现「顺时针旋转」。

.locus {
    // ...
    animation: rotate 15s linear infinite;
    @keyframes rotate {
      0% {
        transform: rotateX(90deg) rotateZ(0deg);
      }
      50% {
        transform: rotateX(90deg) rotateZ(180deg);
      }
      100% {
        transform: rotateX(90deg) rotateZ(360deg);
      }
    }
}

可以看到,当「空间站🛰️」转到前/后面时,变成一条线了。此时,可以在locus旋转的同时,「空间站🛰️」也同步旋转,让其正面一直面向屏幕。

.station {
    // ...

    img {
        // ...
        animation: stationRotate 15s linear infinite;
        @keyframes stationRotate {
          0% {
            transform: rotateY(0deg);
          }
          100% {
            transform: rotateY(360deg);
          }
        }
    }
}

至此,「空间站🛰️」已经基本实现啦,接下来轮到实现「地球🌍」。

地球

众所周知,「空间站🛰️」是围绕地球环绕飞行的,所以,「地球🌍」的位置应处于「空间站🛰️」运行轨迹的中心。

.world {
      position: absolute;
      left: 50%;
      top: 50%;
      z-index: 1;
      width: 450px;
      height: 450px;
      transform: translate(-50%, -50%) rotateX(-90deg) rotateY(0deg);
      border-radius: 50%;
      background: url(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0fb8b640292c416295486e0d06eba909~tplv-k3u1fbpfcp-watermark.image);
      background-size: 900px 450px;
      background-repeat: repeat;
      background-position: 0px 0px;
}

通过「绝对定位」把「地球🌍」放置在正中心。

很明显,这并不符合实际效果,地球应该永远都是圆的。因此,可以和「空间站🛰️」类似,通过自身旋转,让正面永远面对屏幕。

.world {
    // ...
    animation: world 15s linear infinite;
      @keyframes world {
        0% {
          transform: translate(-50%, -50%) rotateX(-90deg) rotateY(0deg);
        }
        100% {
          transform: translate(-50%, -50%) rotateX(-90deg) rotateY(360deg);
        }
      }
}

这样,「地球🌍」永远直视屏幕了,但也不会旋转了。此时,可以利用背景图的位置切换,从而实现滚动的效果。

地球地图⬇️: w.jpg

.world {
    animation: world 15s linear infinite;
    @keyframes world {
        0% {
          // ...
          background-position: 0px 0px;
        }
        100% {
          // ...
          background-position: -900px 0px;
        }
    }
}

多个空间站

如果想在同一轨迹上实现多个「空间站🛰️」环绕,可以通过不同的偏转角度。比如,在该轨迹上添加10个「空间站🛰️」,一圈360deg,也就是如果需要均匀分布,则每个「空间站🛰️」需要在Y轴偏移360deg / 10 = 36deg

<template>
    <div class="locus">
        <div class="station" v-for="(item, index) in new Array(10)" :key="index">
          <img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6f9ac53fa9684dcc96ec99c57bd0c663~tplv-k3u1fbpfcp-watermark.image" />
        </div>
    </div>
</template>
<style>
@for $i from 1 through 10 {
  .station:nth-child(#{$i}) {
    transform: rotateX(-90deg) rotateY(calc(#{($i - 1) * -36deg}));
    img {
      animation: lirotate#{$i} 15s linear 0s infinite;
      transition-property: animation;
      transition-delay: calc(2300ms - #{($i - 1) * 200ms});
    }
  }
  @keyframes lirotate#{$i} {
    0% {
      transform: rotateY(calc(36deg * #{$i - 1})) scale(0.7) rotateZ(0deg);
    }
    100% {
      transform: rotateY(calc(360deg + #{($i - 1) * 36deg})) scale(0.7) rotateZ(0deg);
    }
  }
}
</style>

借助SASS就可以节省很多代码,通过for循环就可以定义每一个「空间站🛰️」的专属动画。

最后

最后把所有用于借助理解的border去掉,把地球加上 ⬇️ :