Skip to content

星系环绕


  • 😊
  • 😁
  • 😉
  • 😜
  • 😎
  • 😆

源代码

vue
<template>
  <ul class="star-box" :class="{ trans: useTransision }">
    <li class="wrap"></li>
    <li
      class="circle"
      ref="starEl"
      v-for="starItem in starList"
      :key="starItem.label"
    >
      <div>{{ starItem.label }}</div>
    </li>
    <li class="inner"></li>
  </ul>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue';

const useTransision = ref(false);
const starEl = ref();
const starList = reactive([
  {
    label: '😊',
  },
  {
    label: '😁',
  },
  {
    label: '😉',
  },
  {
    label: '😜',
  },
  {
    label: '😎',
  },
  {
    label: '😆',
  },
]);

const itemUnit = 360 / starList.length;
const duration = 1000;
const transition = `transform ${duration}ms 0s linear`;

// 更新位置
const updatePosisiton = function (list) {
  const newList = [...list];
  const last = newList.pop();
  newList.unshift(last);

  // 移动完成后重置为初始位置
  const handlePositionReset = function () {
    this.removeEventListener('transitionend', handlePositionReset);
    // 避免出现过渡动画
    this.style.transition = 'none';
    this.style.transform = `rotateX(70deg) rotateZ(0deg)`;
    this.querySelector('div').style.transition = 'none';
    this.querySelector('div').style.transform = `rotateX(-90deg) rotateY(0deg)`;

    // 恢复过渡动画
    setTimeout(() => {
      this.style.transition = transition;
      this.querySelector('div').style.transition = transition;
      updatePosisiton(newList);
    }, 0);
  };

  newList.forEach((item, index) => {
    if (index === 0) {
      item.addEventListener('transitionend', handlePositionReset);
      item.classList.remove('no-border');
      item.style.transform = `rotateX(70deg) rotateZ(360deg)`;
      item.querySelector(
        'div'
      ).style.transform = `rotateX(-90deg) rotateY(360deg)`;
    } else {
      item.classList.add('no-border');
      item.style.transform = `rotateX(70deg) rotateZ(${itemUnit * index}deg)`;
      item.querySelector('div').style.transform = `rotateX(-90deg) rotateY(${
        itemUnit * index
      }deg)`;
    }
    item.style.zIndex = index;
  });
};

// 初始化位置
const initPosition = (list) => {
  list.forEach((item, index) => {
    if (index !== 0) {
      item.classList.add('no-border');
    }
    item.style.transform = `rotateX(70deg) rotateZ(${itemUnit * index}deg)`;
    item.querySelector('div').style.transform = `rotateX(-90deg) rotateY(${
      itemUnit * index
    }deg)`;
    item.style.zIndex = index;
  });
  useTransision.value = true;
};

onMounted(() => {
  initPosition(starEl.value);
  setTimeout(() => {
    updatePosisiton(starEl.value);
  }, 1000);
});
</script>

<style scoped>
ul {
  width: 100%;
  padding-top: 100%;
  list-style: none;
  position: relative;
  list-style: none;
}
li {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  margin: auto;
  border: 1px solid #999;
  border-radius: 50%;
  /* 3d */
  transform-origin: center center;
  transform-style: preserve-3d;
  /* 倾斜角度 */
  transform: rotateX(70deg);
}
.star-box li + li {
  margin-top: auto;
}

.trans li {
  transition: transform 1s 0s linear;
}

.wrap {
  width: 100%;
  height: 100%;
}
.circle {
  width: 80%;
  height: 80%;
}
.circle.no-border {
  border: none;
}
.inner {
  width: 60%;
  height: 60%;
}
.circle div {
  position: absolute;
  top: -22px;
  left: 0;
  right: 0;
  margin: auto;
  text-align: center;
  font-size: 36px;
  transition: all 1s 0s linear;
}
</style>