Vue中实现多个图标围绕中心点旋转

效果如下:如下六个图标围绕平台中心点旋转

在这里插入图片描述

  • template
<!-- 容器 -->
<div class="container">
   <!-- 容器中的每个气泡 -->
   <div
     v-for="item in bubbleList"
     :key="item.name"
     class="bubble-item"
     :style="{
       left: `${
      
      item.x}px`,
       top: `${
      
      item.y}px`,
       transform: `scale(${
      
      item.scale})`,
       transformOrigin: `50% ${
      
      item.transformY}%`,
       zIndex: item.zIndex,
       backgroundImage: `url(${
      
      item.url})`,
       backgroundSize: '100% 100%',
       opacity: item.opacity,
     }"
     :aria-label="item.name?.substring(0, 2)"
     :aria-value="item.name"
     @click="bubbleClick(item)"
   >
     <!-- 气泡中的名字 -->
     <div class="bubble-item-name">{
    
    {
    
     item.name }}</div>
     <!-- 气泡中的数量 -->
     <div class="bubble-item-count" :style="{ color: item.color }">{
    
    {
    
     item.count }}</div>
   </div>
 </div>
  • data数据源
data() {
    
    
  return {
    
    
    /**模板数据 */
    templateList: [
      {
    
    
        name: "探访关爱",
        code: "1",
        count: 100,
        color: "rgba(0, 240, 255, 1)",
        url: require("../../images/visit-care.png"),
      },
      {
    
    
        name: "适老化换新",
        code: "2",
        count: 200,
        color: "rgba(254, 189, 76, 1)",
        url: require("../../images/aging.png"),
      },
      {
    
    
        name: "家庭床位",
        code: "3",
        count: 300,
        color: "rgba(76, 254, 227, 1)",
        url: require("../../images/bed-care.png"),
      },
      {
    
    
        name: "助餐服务",
        code: "4",
        count: 400,
        color: "rgba(251, 150, 255, 1)",
        url: require("../../images/meal.png"),
      },
      {
    
    
        name: "居家服务",
        code: "5",
        count: 400,
        color: "rgba(0, 240, 255, 1)",
        url: require("../../images/visit-care.png"),
      },
      {
    
    
        name: "智能看护",
        code: "6",
        count: 500,
        color: "rgba(76, 204, 254, 1)",
        url: require("../../images/smart-care.png"),
      },
    ],
    /**气泡实例数组 */
    bubbleList: [],
    /**每一帧应该旋转的角度 */
    angle: 0,
    /**最新更新时间 */
    lastTime: null,
    /**是否已经销毁 */
    destroyed: false,
    /**浏览器动画优化实例 */
    animationFrameId: null,
  };
},
  • mounted
mounted() {
    
    
  // requestAnimationFrame是浏览器用于调度动画帧的一种机制,它会根据浏览器的刷新频率来调用回调函数,从而优化动画性能。
  this.animationFrameId = requestAnimationFrame(this.updateScenePosition);
  this.$once("hook:beforeDestroy", () => {
    
    
    cancelAnimationFrame(this.animationFrameId);
    this.animationFrameId = null;
  });
},
  • methods
methods: {
    
    
  updateScenePosition(timestamp) {
    
    
    if (this.destroyed) return;
    // 初始化开始时间
    if (!this.lastTime) this.lastTime = timestamp;
    // 计算时间差(毫秒)
    const deltaTime = timestamp - this.lastTime;
    this.lastTime = timestamp;
    // 设定每秒旋转的角度(比如90度/秒)
    const rotationSpeed = 20; // 度/秒
    // 根据时间差计算这一帧应该旋转的角度
    this.angle = (this.angle + (rotationSpeed * deltaTime) / 1000) % 360;
    this.calculateItemPosition(this.angle);
    this.animationFrameId = requestAnimationFrame(this.updateScenePosition);
  },
  /**
   * 动画icon点击事件
   * @param item
   */
  bubbleClick(item) {
    
    },
  /**
   * 计算列表中item的坐标,根据item的index计算坐标
   * @param {
    
    *} list
   * @param {
    
    number} angle 椭圆的角度
   * @returns
   */
  calculateItemPosition(angle = 0) {
    
    
    // 深拷贝一份模板数据
    const list = this.toolClass.deepClone(this.templateList);
    const a = 200; // 椭圆的水平半轴
    const b = 100; // 椭圆的垂直半轴
    const h = 400; // 椭圆的中心点x
    const k = 200; // 椭圆的中心点y
    let n = list.length; // 总项数
    let angleInPI = (2 * Math.PI * angle) / 360;
    list.forEach((item, i) => {
      const index = i;
      const theta = (2 * Math.PI * index) / n;
      const x = a * Math.cos(theta + angleInPI) + h;
      const y = b * Math.sin(theta + angleInPI) + k;
      // 由于item尺寸从100px变为110px,需要将偏移量从50调整为55
      item.x = x - 55; // 调整x坐标使得item的中心对准椭圆上的坐标
      item.y = y - 55; // 调整y坐标使得item的中心对准椭圆上的坐标
      const itemAngle = ((360 * index) / n + angle) % 360;
      let scale = 1;
      let translateY = "0px";
      let transformY = 50;
      if (itemAngle < 180) {
        scale = 1 - 0.3 * Math.abs((itemAngle - 90) / 90);
        item.opacity = 1;
      } else {
        scale = 0.5 + 0.2 * Math.abs((itemAngle - 270) / 90);
        transformY = 50 * (1 - Math.abs((itemAngle - 270) / 90));
        item.opacity = 0.3 + 0.4 * Math.abs((itemAngle - 270) / 90);
      }
      item.translateY = translateY;
      item.scale = scale;
      item.angle = itemAngle.toFixed(1);
      item.zIndex = itemAngle < 180 ? 1 : 0;
    });
    // 更新气泡实例数组
    this.bubbleList = list;
  },
},
  • style
.container {
    
    
  width: 800px;
  height: 400px;
  position: relative;
  // 背景
  background: url("@/views/bed-care/images/bed-care-bg.png") no-repeat;
  background-size: 100% 100%;
  .bubble-item {
    
    
    position: absolute;
    width: 110px;
    height: 110px;
    cursor: pointer;
    transition: all 0.3s;
    border-radius: 50%;
    display: flex;
    justify-content: center;
    align-items: center;
    .bubble-item-name {
    
    
      font-family: Adobe Heiti Std;
      font-weight: normal;
      font-size: 14px;
      color: #ffffff;
    }
    .bubble-item-count {
    
    
      font-family: DingTalk JinBuTi;
      font-weight: 400;
      font-size: 20px;
      margin-top: 5px;
    }
  }
}