vue2、vue3 纯SCSS 实现环形进度条

vue3 纯SCSS 实现环形进度条


<template>
  <view class="flex align-center diygw-col-24 justify-center">
    <view
      class="progress-circle"
      :class="`progress-${innerPercent}`"
    >
      <view class="inner">
        <slot></slot>
      </view>
    </view>
  </view>
</template>
 
<script lang="ts" setup>
const props = defineProps({
  width: {
    type: Number || String,
    default: '56px'
  },
  borderWidth: {
    type: Number || String,
    default: '3px'
  },
  bgColor: {
    type: String,
    default: '#fff'
  },
  notProgressColor: {
    type: String,
    default: '#ddd'
  },
  progressColor: {
    type: String,
    default: '#004898'
  },
  color: {
    type: String,
    default: '#333'
  },
  fontSize: {
    type: String,
    default: '24rpx'
  },
  percent: {
    type: Number,
    default: 0
  },
  animate: {
    type: Boolean,
    default: true
  },
  rate: {
    type: Number,
    default: 5
  }
});

const innerPercent = ref(props.percent);

const complete = computed(() => innerPercent.value == 100);

watch(() => props.percent, (percent) => {
  setPercent();
});

onMounted(() => {
  setPercent();
});

const setPercent = () => {
  if (props.animate) {
    stepTo(true);
  } else {
    innerPercent.value = props.percent;
  }
};
const stateTimer = ref(null)
const clearTimeoutFn = () => {
  if (stateTimer.value) {
    clearTimeout(stateTimer.value);
    stateTimer.value = null;
  }
};

const stepTo = (topFrame = false) => {
  if (topFrame) {
    clearTimeoutFn();
  }
  if (props.percent > innerPercent.value && !complete.value) {
    innerPercent.value = innerPercent.value + 1;
  }
  if (props.percent < innerPercent.value && innerPercent.value > 0) {
    innerPercent.value = innerPercent.value - 1;
  }
  if (innerPercent.value !== props.percent) {
    stateTimer.value = setTimeout(() => {
      stepTo();
    }, props.rate);
  }
};
</script>
 
<style lang="scss" scoped>
.progress-circle {
  $diythemeColor: v-bind('props.progressColor');
  $diybackColor: v-bind('props.notProgressColor');
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: v-bind('props.width');
  height: v-bind('props.width');
  border-radius: 50%;
  transition: transform 1s;
  background-color: $diybackColor;
  padding: v-bind('props.borderWidth');
  box-sizing: border-box;

  .inner {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    z-index: 1;
    background-color: v-bind('props.bgColor');
  }

  &:before {
    content: '';
    left: 0;
    top: 0;
    position: absolute;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    background-color: $diythemeColor;
  }

  $step: 1;
  $loops: 99;
  $increment: 3.6;
  $half: 50;

  @for $i from 0 through $loops {
    &.progress-#{$i * $step}:before {
      @if $i < $half {
        $nextDeg: 90deg+($increment * $i);
        background-image: linear-gradient(90deg, $diybackColor 50%, transparent 50%, transparent), linear-gradient($nextDeg, $diythemeColor 50%, $diybackColor 50%, $diybackColor);
      }

      @else {
        $nextDeg: -90deg+($increment * ($i - $half));
        background-image: linear-gradient($nextDeg, $diythemeColor 50%, transparent 50%, transparent), linear-gradient(270deg, $diythemeColor 50%, $diybackColor 50%, $diybackColor);
      }
    }
  }

  .progress-number {
    width: 100%;
    line-height: 1;
    text-align: center;
    font-size: v-bind('props.fontSize');
    color: v-bind('props.color');
  }
}
</style>

vue2 纯SCSS 实现环形进度条


<template>
  <view class="flex align-center diygw-col-24 justify-center">
    <view class="progress-circle " :class="'progress-' + innerPercent" :style="{
      '--not-progress-color': notProgressColor,
      '--bg-color': bgColor,
      '--color': color,
      '--progress-color': progressColor,
      '--width': $u.addUnit(width),
      '--font-size': $u.addUnit(fontSize),
      '--border-width': $u.addUnit(borderWidth)
    }">
      <view class="inner">
        <!-- <view class="progress-number">{{ innerPercent }}%</view> -->
        <slot></slot>
      </view>
    </view>
  </view>
</template>
 
<script>
export default {
  props: {
    width: {
      type: Number,
      default: 52
    },
    borderWidth: {
      type: Number,
      default: 3
    },
    bgColor: {
      type: String,
      default: '#fff'
    },
    notProgressColor: {
      type: String,
      default: '#ddd'
    },
    progressColor: {
      type: String,
      default: '#004898'
    },
    color: {
      type: String,
      default: '#333'
    },
    fontSize: {
      type: Number,
      default: 24
    },
    /**
     * 进度(0-100)
     */
    percent: {
      type: Number,
      default: 0
    },
    /**
     * 是否动画
     */
    animate: {
      type: Boolean,
      default: true
    },
    /**
     * 动画速率
     */
    rate: {
      type: Number,
      default: 5
    }
  },
  computed: {
    /**
     * @private
     */
    complete() {
      return this.innerPercent == 100
    }
  },
  watch: {
    percent(percent) {
      this.setPercent()
    }
  },
  data() {
    return {
      innerPercent: 0,
      timeout: null
    }
  },
  mounted() {
    this.setPercent()
  },
  methods: {
    setPercent() {
      if (this.animate) {
        this.stepTo(true)
      } else {
        this.innerPercent = this.percent
      }
    },
    clearTimeout() {
      clearTimeout(this.timeout)
      Object.assign(this, {
        timeout: null
      })
    },
    stepTo(topFrame = false) {
      if (topFrame) {
        this.clearTimeout()
      }
      if (this.percent > this.innerPercent && !this.complete) {
        this.innerPercent = this.innerPercent + 1
      }
      if (this.percent < this.innerPercent && this.innerPercent > 0) {
        this.innerPercent--
      }
      if (this.innerPercent !== this.percent) {
        this.timeout = setTimeout(() => {
          this.stepTo()
        }, this.rate)
      }
    }
  }
}
</script>
 
<style lang="scss" scoped>
.progress-circle {
  --progress-color: #63B8FF;
  --not-progress-color: #ddd;
  --bg-color: #fff;
  --width: 240rpx;
  --border-width: 10rpx;
  --color: #777;
  --font-size: 1.5rem;

  $diythemeColor: var(--progress-color);
  $diybackColor: var(--not-progress-color);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--width);
  height: var(--width);
  border-radius: 50%;
  transition: transform 1s;
  background-color: $diybackColor;
  padding: var(--border-width);

  .inner {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    z-index: 1;
    background-color: var(--bg-color);
  }

  &:before {
    content: '';
    left: 0;
    top: 0;
    position: absolute;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    background-color: $diythemeColor;
  }

  $step: 1;
  $loops: 99;
  $increment: 3.6;
  $half: 50;

  @for $i from 0 through $loops {
    &.progress-#{$i * $step}:before {
      @if $i < $half {
        $nextDeg: 90deg+($increment * $i);
        background-image: linear-gradient(90deg, $diybackColor 50%, transparent 50%, transparent), linear-gradient($nextDeg, $diythemeColor 50%, $diybackColor 50%, $diybackColor);
      }

      @else {
        $nextDeg: -90deg+($increment * ($i - $half));
        background-image: linear-gradient($nextDeg, $diythemeColor 50%, transparent 50%, transparent), linear-gradient(270deg, $diythemeColor 50%, $diybackColor 50%, $diybackColor);
      }
    }
  }

  .progress-number {
    width: 100%;
    line-height: 1;
    text-align: center;
    font-size: var(--font-size);
    color: var(--color);
  }
}
</style>

使用

 <CircleProgress
     :percent="item.curSoc"
     style="position: relative;"
     class="full-box">
    <view class="number" style="position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: max-content; white-space: nowrap;">
      <template v-if="item.electricCurrentModel === 1">
        <view style="font-size: 28rpx; font-weight: bold;">{{item.curSoc}}%</view>
        <view style="font-size: 18rpx;">充电中</view>
      </template>
      <view v-else>充电中</view>
    </view>
 </CircleProgress>
posted @ 2024-07-02 10:22  DL·Coder  阅读(144)  评论(0编辑  收藏  举报