扇形旋转切换效果(等级切换转盘)
2024-10-16 09:30 阅读(196)

实现动态扇形旋转切换效果,切换进度支持渐变效果


效果展示

原理拆解

环形进度条:使用上下两个相同大小的圆间隔一定距离覆盖得到一条圆环

进度条渐变及进度控制:通过一个从左至右渐变的矩形覆盖在圆环上,然后通过css变量动态控制矩形的宽度实现进度控制

等级旋转切换:将等级按照指定间隔角度定位到圆的边上,通过改变圆的旋转角度实现等级旋转切换


源码实现

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    .position-center {
      position: absolute;
      left: 50%;
      transform: translateX(-50%);
      bottom: 0;
    }

    .container {
      --height: 20vh;
      --progress: 0;

      width: 100%;
      height: var(--height);
      position: relative;
      overflow: hidden;

      .inner {
        width: 200%;
        height: calc(var(--height) * 2);
        background-color: #2f2f2f;
        border-radius: 50%;
        overflow: hidden;

        .circle {
          width: calc(var(--height) * 6.5);
          height: calc(var(--height) * 6.5);
          border-radius: 50%;
        }

        .circle-bottom {
          bottom: 12%;
          overflow: hidden;
          padding: 25% 15% 0 15%;
          background-color: #535353;

          .circle-mask {
            width: calc(var(--progress) * 1%);
            height: 100%;
            background-image: linear-gradient(to right, rgba(31, 231, 236, .3), rgba(31, 231, 236, .7));
            transition: all .3s ease-in-out;
          }
        }

        .circle-top {
          background-color: #2f2f2f;
          bottom: 13%;
          padding: 27% 15% 0 15%;

          color: #fff;
          display: flex;
          justify-content: space-around;
          align-items: flex-end;
        }

        .circle-main {
          width: calc(var(--height) * 6.5);
          height: calc(var(--height) * 6.5);
          border-radius: 50%;
          transition: all .3s ease-in-out;
          transform: translateX(-50%) rotate(0deg);

          .item {
            --rotate: 0;
            position: absolute;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: flex-end;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%) rotate(calc(var(--rotate) * -1deg));

            .item-inner {
              display: flex;
              flex-direction: column;
              align-items: center;
              position: relative;
              bottom: -30px;
              font-size: 14px;
              color: #ccc;

              .point {
                width: 7px;
                height: 7px;
                background-color: #fff;
                border-radius: 50%;
                margin-top: 4px;
                box-shadow: 0 0 10px rgba(255, 255, 255, 0.8);


                &::before {
                  content: '';
                  width: 12px;
                  height: 12px;
                  border-radius: 50%;
                  position: absolute;
                  top: 50%;
                  left: 50%;
                  transform: translate(-50%, -50%);
                }
              }

              .label-bottom {
                margin-top: 5px;
              }
            }

            .active {
              .point {
                background-color: rgba(31, 231, 236, 1);

                &::before {
                  background-color: rgba(31, 231, 236, 0.3);
                }
              }
            }
          }
        }
      }
    }

    .btns {
      position: absolute;
      bottom: 500px;
      left: 50%;
      transform: translateX(-50%);
      button {
        color: #1fe7ec;
        border: 1px solid #1fe7ec;
        background-color: transparent;
        padding: 4px 15px;
        border-radius: 4px;
        font-size: 14px;
      }
    }
  </style>
</head>

<body>
  <div id="container" class="container" style="--progress: 33.33">
    <div class="inner position-center">
      <div class="circle circle-bottom position-center">
        <div class="circle-mask"></div>
      </div>
      <div class="circle circle-top position-center">
        <div id="circle" class="circle-main position-center">
          <div class="item" style="--rotate: -15;">
            <div class="item-inner active">
              <div class="label-top">10-15w</div>
              <div class="point"></div>
              <div class="label-bottom">旅行家 V1</div>
            </div>
          </div>
          <div class="item" style="--rotate: 0;">
            <div class="item-inner">
              <div class="label-top">15-20w</div>
              <div class="point"></div>
              <div class="label-bottom">旅行家 V2</div>
            </div>
          </div>
          <div class="item" style="--rotate: 15;">
            <div class="item-inner">
              <div class="label-top">20w+</div>
              <div class="point"></div>
              <div class="label-bottom">旅行家 V3</div>
            </div>
          </div>
          <div class="item" style="--rotate: 30;">
            <div class="item-inner">
              <div class="label-top">30w+</div>
              <div class="point"></div>
              <div class="label-bottom">旅行家 V4</div>
            </div>
          </div>
          <div class="item" style="--rotate: 45;">
            <div class="item-inner">
              <div class="label-top">50w+</div>
              <div class="point"></div>
              <div class="label-bottom">旅行家 V5</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

  <div class="btns">
    <button onclick="prev()">上一个</button>
    <button onclick="next()">下一个</button>
  </div>

  <script>
    const container = document.getElementById('container')
    const circle = document.getElementById('circle')
    const max = circle.children.length

    let currentIndex = 0

    const acitve = () => {
      const items = circle.querySelectorAll('.item')
      items.forEach((item, index) => {
        const itemInner = item.querySelector('.item-inner')
        if (index === currentIndex) {
          itemInner.classList.add('active')
        } else {
          itemInner.classList.remove('active')
        }
      })
    }
    const next = () => {
      if (currentIndex < max - 1) {
        currentIndex += 1
      }

      if (currentIndex < max - 1) {
        container.style.setProperty('--progress', 50)
        circle.style.transform = `translateX(-50%) rotate(${15 * (currentIndex - 1)}deg)`
      } else {
        container.style.setProperty('--progress', 100)
      }

      acitve()
    }

    const prev = () => {
      if (currentIndex > 0) {
        currentIndex -= 1
      }

      if (currentIndex > 0) {
        container.style.setProperty('--progress', 50)
        circle.style.transform = `translateX(-50%) rotate(${15 * (currentIndex - 1)}deg)`
      } else {
        container.style.setProperty('--progress', 33.33)
      }

      acitve()
    }
  </script>
</body>

</html>