vue3实现圆环进度条组件

前言

使用vue3实现一个圆环进度条

带有加载动画、中间为插槽、背景可以设置渐变效果

效果展示

完整代码如下

<template>
  <div class="percentloop" ref="percentloop">
    <div class="circle-left">
      <div ref="leftcontent"></div>
    </div>
    <div class="circle-right">
      <div ref="rightcontent"></div>
    </div>
    <div class="internal">
      <slot></slot>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted, getCurrentInstance, watch } from "vue";
defineOptions({ name: "TaskCircle" });
const props = defineProps({
  /**
   * 说明:进度百分比
   * 可选值:0-100
   */
  percentNum: {
    type: [String, Number],
    required: false,
    default: 0,
  },
  /**
   * 说明:动画加载速度
   * 可选值:推荐2-5
   */
  speed: {
    type: [String, Number],
    required: false,
    default: 4,
  },
  /**
   * 说明:圆环进度条大小(单位:px)
   */
  size: {
    type: [String, Number],
    required: false,
    default: 260,
  },
});

const data = reactive({
  percent: 0,
  initDeg: 0,
  timeId: null,
  animationing: false,
});

// 获取dom元素
const percentloop = ref(null);
const leftcontent = ref(null);
const rightcontent = ref(null);

const methods = reactive({
  transformToDeg(percent) {
    let deg = 0;
    if (percent >= 100) {
      deg = 360;
    } else {
      deg = parseInt((360 * percent) / 100);
    }
    return deg;
  },
  transformToPercent(deg) {
    let percent = 0;
    if (deg >= 360) {
      percent = 100;
    } else {
      percent = parseInt((100 * deg) / 360);
    }
    return percent;
  },
  rotateLeft(deg) {
    // 大于180时,执行的动画
    if (leftcontent.value?.style)
      leftcontent.value.style.transform = "rotate(" + (deg - 180) + "deg)";
  },
  rotateRight(deg) {
    // 小于180时,执行的动画
    if (rightcontent.value?.style)
      rightcontent.value.style.transform = "rotate(" + deg + "deg)";
  },
  goRotate(deg) {
    data.animationing = true;
    data.timeId = setInterval(() => {
      if (deg > data.initDeg) {
        // 递增动画
        data.initDeg += Number(props.speed);
        if (data.initDeg >= 180) {
          methods.rotateLeft(data.initDeg);
          methods.rotateRight(180); // 为避免前后两次传入的百分比转换为度数后的值不为步距的整数,可能出现的左右转动不到位的情况。
        } else {
          methods.rotateRight(data.initDeg);
        }
      } else {
        // 递减动画
        data.initDeg -= Number(props.speed);
        if (data.initDeg >= 180) {
          methods.rotateLeft(data.initDeg);
        } else {
          methods.rotateLeft(180); // 为避免前后两次传入的百分比转换为度数后的值不为步距的整数,可能出现的左右转动不到位的情况。
          methods.rotateRight(data.initDeg);
        }
      }
      data.percent = methods.transformToPercent(data.initDeg); // 百分比数据滚动动画
      const remainer = Number(deg) - data.initDeg;
      if (Math.abs(remainer) < props.speed) {
        data.initDeg += remainer;
        if (data.initDeg > 180) {
          methods.rotateLeft(deg);
        } else {
          methods.rotateRight(deg);
        }
        methods.animationFinished();
      }
    }, 10);
  },
  animationFinished() {
    data.percent = props.percentNum; // 百分比数据滚动动画
    data.animationing = false;
    clearInterval(data.timeId);
  },
});

onMounted(() => {
  methods.goRotate(methods.transformToDeg(props.percentNum));
  percentloop.value.style.height = props.size + "px";
  percentloop.value.style.width = props.size + "px";
});
watch(
  () => props.percentNum,
  (val) => {
    if (data.animationing) return;
    methods.goRotate(methods.transformToDeg(val));
  }
);
</script>

<style scoped lang="scss">
.percentloop {
  position: relative;
  width: 260px;
  height: 260px;
  border-radius: 50%;
  overflow: hidden;
  .circle-left,
  .circle-right {
    position: absolute;
    top: 0;
    left: 0;
    width: 50%;
    height: 100%;
    // 设置进度条颜色
    background: linear-gradient(180deg, #00f9ff, #0095d4);
    overflow: hidden;
    & > div {
      width: 100%;
      height: 100%;
      background: #04173a;
      opacity: 0.9;
      transform-origin: right center;
    }
  }
  .circle-right {
    left: 50%;
    & > div {
      transform-origin: left center;
    }
  }
  .internal {
    position: absolute;
    // 设置进度条宽度
    top: 3%;
    bottom: 3%;
    left: 3%;
    right: 3%;
    background-color: #010d23;
    border-radius: 50%;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
  }
}
</style>

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以通过计算属性和绑定样式实现进度条根据参数动态变化。 首先,在 Vue3 中创建一个组件,将参数作为组件的 props 传入。然后,使用计算属性计算出进度条的样式,并将样式绑定到对应的 HTML 元素上。最后,使用 CSS 实现的效果。 下面是一个示例代码: ```vue <template> <div class="progress-ring"> <div class="progress-bar" :style="progressStyle"></div> <div class="progress-label">{{ progress }}%</div> </div> </template> <script> export default { props: { progress: { type: Number, required: true, }, }, computed: { // 计算进度条的样式 progressStyle() { const progress = this.progress <= 100 ? this.progress : 100; const rotation = `rotate(${(progress / 100) * 360}deg)`; return { transform: rotation, }; }, }, }; </script> <style scoped> .progress-ring { position: relative; width: 100px; height: 100px; } .progress-bar { position: absolute; width: 100%; height: 100%; border-radius: 50%; clip: rect(0, 50px, 100px, 0); background-color: #f2f2f2; transform: rotate(0deg); z-index: -1; } .progress-bar::before { content: ""; position: absolute; width: 100%; height: 100%; border-radius: 50%; clip: rect(0, 50px, 100px, 0); background-color: #2196f3; transform-origin: center center; } .progress-label { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24px; font-weight: bold; color: #2196f3; } </style> ``` 在上面的代码中,我们定义了一个名为 `progress-ring` 的容器,容器中包含一个进度条和一个显示进度百分比的标签。通过计算属性 `progressStyle` 计算出进度条的样式,并将样式绑定到 `progress-bar` 元素上。最后,使用 CSS 实现的效果。 你可以将这个组件作为子组件嵌入到父组件中,在父组件中传入进度参数即可实现根据参数动态变化的进度条。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值