支持「多次触发」的「伪进度」功能

130 阅读1分钟

运用示例

伪进度.gif

点击前往 Demo - Codepen

示例代码

<template>
  <div style="display: flex; gap: 12px;">
    <div v-for="item in 2" :key="item">
      <el-progress 
         type="dashboard"
         :color="colors"
         :percentage="percentages[item - 1]"
      ></el-progress>
      <div>
        <el-button-group>
          <el-button @click="start(item - 1)">开始</el-button>
          <el-button @click="done(item - 1)">结束</el-button>
        </el-button-group>
      </div>
  </div>
</template>

<script>
import { ProgressBar } from './xxx'

export default {
  data() {
    return {
      percentages: [0, 0],
      progressBars: [null, null],
      colors: [
        {color: '#f56c6c', percentage: 20},
        {color: '#e6a23c', percentage: 40},
        {color: '#5cb87a', percentage: 60},
        {color: '#1989fa', percentage: 80},
        {color: '#6f7ad3', percentage: 100}
      ],
    };
  },
  methods: {
    start(index) {
      this.progressBars[index] = new ProgressBar({
        callback: (percentage) => {
          this.$set(this.percentages, index, Number((percentage * 100).toFixed(2)))
        }
      })
      
      this.progressBars[index].start()
    },
    done(index) {
      this.progressBars[index].done()
    }
  }
}
</script>

功能源码

class ProgressBar {
  constructor({ trickleRate = 0.04, trickleSpeed = 100, callback }) {
    this.intervalId = null;
    this.trickleRate = trickleRate;
    this.trickleSpeed = trickleSpeed;

    let tempProgress = 0;
    Object.defineProperty(this, "progress", {
      get: () => {
        return tempProgress;
      },
      set: (newVal) => {
        tempProgress = newVal;
        // 同步更新数据
        callback && callback(newVal);
      }
    });
  }

  start() {
    if (this.intervalId) {
      return;
    }

    this.intervalId = setInterval(() => {
      this.inc(Math.random() * this.trickleRate);
    }, this.trickleSpeed);
  }

  clamp(val, min, max) {
    if (val < min) {
      return min;
    }
    if (val > max) {
      return max;
    }

    return val;
  }
  
  set(val) {
    this.progress = val;
    return this
  }
  
  inc(val) {
    const endWaitVal = 0.994;
    const formatVal = this.clamp(this.progress + val, 0, endWaitVal);
    // 到达既定停止值时,清除计时器
    formatVal === endWaitVal && clearInterval(this.intervalId);

    return this.set(formatVal)
  }

  stop() {
    if (this.intervalId) {
      clearInterval(this.intervalId);
      // 清空计时器ID,便于重新发起
      this.intervalId = null;
    }
  }

  done() {
    this.inc(0.012 * Math.random() * 0.5).set(1);
    this.stop(); // 停止进度增加
  }
}

源码介绍

类构造函数

constructor({ trickleRate = 0.04, trickleSpeed = 200, callback }) {
  this.intervalId = null;
  this.trickleRate = trickleRate;
  this.trickleSpeed = trickleSpeed;

  let tempProgress = 0;
  Object.defineProperty(this, "progress", {
    get: () => {
      return tempProgress;
    },
    set: (newVal) => {
      tempProgress = newVal;
      // 同步更新数据
      callback && callback(newVal);
    }
  });
}

参数

  • trickleRate:单次增长最高额度,默认值为 0.04(即4%)。
  • trickleSpeed:增长时间间隔,默认值为 200ms
  • callback:进度发生时所调用的回调方法,该方法的首个参数为 最新进度

QNA

Q: 为何不直接赋值?
A: 借助Object.defineProperty下属set方法,能够在进度发生变化时,及时通知该类的使用处。

Q: getset中,为何不直接 获取、赋值 this.progress
A:从代码解析角度出发,不难发现:在使用Object.defineProperty构建参数的过程中,参数本身仍是不存在的。因而需借助外部参数tempProgress

set函数

set(val) {
  this.progress = val;
  return this
}

return this 便于 链式反应 执行