Skip to content
On this page

使用css和定时器实现环形刻度进度条

因为项目还需要开发微信小程序端,所以兼容性是要解决的一个大问题,原本的环形刻度进度条是使用canvas写的,但是在小程序端问题就显露出来了,因为在小程序中canvas是属于原生组件,是不允许放置在scroll-view里面的,就算只是使用css的属性overflow让页面可以滚动,在页面滚动的时候,进度条还是会发生错位的现象(真机上调试),另外在app端测试的时候ios14系统上会显示不全(能力有限,无法解决),所以只能用css和js定时器重新写一个环形刻度进度条

效果图

效果图

原理解析

主要原理就是利用js定时器和css属性transfrom:rotate(xdeg)属性来实现遮罩动画,利用ps中的图层原理和overflow:hidden来实现圆环轨道

原理图解

圆形容器在这里插入图片描述画轨道在这里插入图片描述在这里插入图片描述在这里插入图片描述制作遮罩层在这里插入图片描述遮罩动画在这里插入图片描述

源码

html
<template>
  <view class="progress_box">
    <view class="inner_box">
      <view class="progress_circle position_view">
        <view class="p_left">
          <view class="left_mask" :style="{transform: 'rotate('+rotate2+'deg)'}"></view>
        </view>
        <view class="p_right">
          <view class="right_mask" :style="{transform: 'rotate('+rotate1+'deg)'}"></view>
        </view>
      </view>
      <view class="mask_circle position_view"></view>
      <view class="progress_txt">
        <view>
          <view class="progress_info">
            <text class="txt">{{progress_txt}}</text>
            <text>%</text>
          </view>
          <text class="text-gray" :style="{fontSize:13 + 'px'}">里程超越用户</text>
        </view>
      </view>
      <view class="yuanpan">
        <!-- 刻度,一共40个刻度,刻度数量自己调整 -->
        <view
          class="kedu"
          v-for="item in 40"
          :key="item"
          :style="{transform: 'rotate('+item*9+'deg)'}"
        ></view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      rotate1: 180,
      rotate2: 180,
      rotate1Inter: null,
      rotate2Inter: null,
    };
  },
  props: ['duration', 'progress_txt'],
  mounted() {
    // this.draw()
  },
  watch: {
    progress_txt: function () {
      this.rotate1 = 180;
      this.rotate2 = 180;
      this.draw();
    },
  },
  methods: {
    draw() {
      let step = 360 / 100;
      let progress = (180 / (this.duration / 2)) * (50 / 1000);
      if (this.progress_txt > 50) {
        let maxRotate = (this.progress_txt - 50) * step + 180;
        this.rotate1Inter = setInterval(() => {
          this.rotate1 = this.rotate1 + progress;
          if (this.rotate1 >= 360) {
            this.rotate1 = 0;
            clearInterval(this.rotate1Inter);
            this.rotate2Inter = setInterval(() => {
              this.rotate2 = this.rotate2 + progress;
              if (this.rotate2 >= maxRotate) {
                // this.rotate2 = 0
                clearInterval(this.rotate2Inter);
              }
            }, 50);
          }
        }, 50);
      } else {
        let maxRotate = this.progress_txt * step + 180;
        this.rotate1Inter = setInterval(() => {
          this.rotate1 = this.rotate1 + progress;
          if (this.rotate1 >= maxRotate) {
            clearInterval(this.rotate1Inter);
          }
        }, 50);
      }
    },
  },
};
</script>

<style lang="scss" scoped>
.progress_box {
  display: flex;
  justify-content: center;
  align-items: center;

  .inner_box {
    height: 300rpx;
    width: 300rpx;
    position: relative;
    overflow: hidden;
    border-radius: 50%;
    .position_view {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translateX(-50%) translateY(-50%);
    }

    .progress_circle {
      position: absolute;
      height: 300rpx;
      width: 300rpx;
      border-radius: 50%;
      z-index: 1000;
      overflow: hidden;
      display: flex;

      // background: #5AA2FC;
      .p_left {
        width: 150rpx;
        height: 300rpx;
        position: relative;
        overflow: hidden;
        background: linear-gradient(to left, #00f2fe, #4facfe);

        .left_mask {
          // top right bottom left
          position: absolute;
          top: 0;
          left: 0rpx;
          width: 300rpx;
          height: 300rpx;
          border-radius: 50%;
          clip: rect(0rpx, 300rpx, 300rpx, 150rpx);
          background: rgb(230, 227, 232);
        }
      }

      .p_right {
        width: 150rpx;
        height: 300rpx;
        background: linear-gradient(to right, #00f2fe, #4facfe);
        position: relative;
        overflow: hidden;

        .right_mask {
          position: absolute;
          top: 0;
          left: -150rpx;
          width: 300rpx;
          height: 300rpx;
          border-radius: 50%;
          clip: rect(0rpx, 150rpx, 300rpx, 0rpx);
          background: rgb(230, 227, 232);
        }
      }
    }

    .mask_circle {
      height: 254rpx;
      width: 254rpx;
      background: rgb(255, 255, 255);
      border-radius: 50%;
      z-index: 1001;
    }

    .progress_txt {
      position: absolute;
      top: 0;
      left: 0;
      width: 300rpx;
      height: 300rpx;
      font-size: 28rpx;
      color: #999999;
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 9999;
    }

    .progress_info {
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 52rpx;
      color: #333333;
      font-size: 36rpx;
    }

    .txt {
      text-align: center;
      font-size: 76rpx !important;
      font-weight: 500;
    }
  }
}

.yuanpan {
  margin: 0;
  padding: 0;
  height: 300rpx;
  height: 300rpx;
  // position: relative;
  position: absolute;
  left: -6rpx;
}

.yuanpan .kedu {
  width: 10rpx;
  height: 30rpx;
  background: white;
  position: absolute;
  left: 150rpx;
  top: 0;
  -webkit-transform-origin: center 152rpx;
  z-index: 99999;
}
</style>