How to create a circle progress inside a Vue component How to create a circle progress inside a Vue component vue.js vue.js

How to create a circle progress inside a Vue component


You'll need to familiarize yourself with SVG shapes, in particular <path> in order to make the arc.

Here's an example:

Vue.component('progress-ring', {  template: '#progress-ring',  props: {    value: {      type: Number,      default: 0,    },    min: {      type: Number,      default: 0,    },    max: {      type: Number,      default: 1,    },    text: {      type: null,      default: '',    },  },  computed: {    theta() {      const frac = (this.value - this.min) / (this.max - this.min) || 0;      return frac * 2 * Math.PI;    },    path() {      const large = this.theta > Math.PI;      return `M0,-46 A46,46,0,${large ? 1 : 0},1,${this.endX},${this.endY}`;    },    endX() {      return Math.cos(this.theta - Math.PI * 0.5) * 46;    },    endY() {      return Math.sin(this.theta - Math.PI * 0.5) * 46;    },  },});new Vue({  el: '#app',});
body {  font-family: sans-serif;}.progress-ring {  width: 100px;  height: 100px;}.progress-ring-circle {  stroke: rgba(0, 0, 0, 0.1);  stroke-width: 1;  fill: none;}.progress-ring-ring {  stroke: #007fff;  stroke-width: 2;  fill: none;}.progress-ring-end {  fill: #007fff;}
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script><div id="app">  <progress-ring :min="0" :max="100" :value="40" text="12:34"></progress-ring></div><template id="progress-ring">  <svg class="progress-ring" viewBox="-50,-50,100,100">    <circle class="progress-ring-circle" r="46"/>    <path class="progress-ring-ring" :d="path"/>    <circle class="progress-ring-end" :cx="endX" :cy="endY" r="4"/>    <text alignment-baseline="middle" text-anchor="middle">{{ text }}</text>  </svg></template>

As for animating it, you just need to use JavaScript to change the value prop by using, for example, setInterval or some other means.


Follow your template, one solution is pre-define the path into one array (each path node is one element of the array). Then push the path node to current progress path for each interval.

Like below demo:

var app = new Vue({  el: '#app',  data: {    date: moment(2 * 60 * 1000),    pathRoute: ['M45 5', 'c22.1 0 40 17.9 40 40','S67.1 85 45 85','S5 67.1 5 45','S22.9 5 45 5'],    pathProgess: [],    stepIndex: 0  },  computed: {    time: function(){      return this.date.format('mm:ss');    },    computedProgress: function () {      return this.pathProgess.join(' ')    }  },  mounted: function(){  	var timer = setInterval(() => {      this.date = moment(this.date.subtract(1, 'seconds'));      this.$set(this.pathProgess, this.stepIndex, this.pathRoute[this.stepIndex])      this.stepIndex++      if(this.date.diff(moment(0)) === 0){        clearInterval(timer);      }    }, 1000);  }});
.st0{fill:#FFFFFF;}.st1{fill:none;stroke:#B5B5B5;stroke-miterlimit:10;}.st2{fill:none;stroke:#408EFF;stroke-linecap:round;stroke-miterlimit:10;}.st3{fill:#408EFF;}
<script src="https://momentjs.com/downloads/moment.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script><div id="app"><p>{{computedProgress}}</p><svg x="0px" y="0px" viewBox="0 0 90 90">    <rect class="st0" width="90" height="90"/>    <circle class="st1" cx="45" cy="45" r="40"/>    <text class="circle-chart-percent" x="20.91549431" y="40.5" font-size="8">{{time}}</text>    <path class="st2" :d="computedProgress"/>    <circle class="st3" cx="45" cy="5" r="3"/></svg></div>

Or you can use the approach Answered at another question, to real time calculate the path.

var app = new Vue({  el: '#app',  data: {    date: moment(2 * 60 * 1000),    pathProgess: ''  },  computed: {    time: function(){      return this.date.format('mm:ss');    }  },  mounted: function(){    let maxValue = this.date.diff(moment(0), 'seconds') //total seconds  	var timer = setInterval(() => {      this.date = moment(this.date.subtract(1, 'seconds'))      let curValue = this.date.diff(moment(0), 'seconds') // current seconds      this.pathProgess = this.describeArc(45, 45, 40, 0, (maxValue-curValue)*360/maxValue)      if(this.date.diff(moment(0)) === 0){        clearInterval(timer);      }    }, 1000);  },  methods: {      //copy from https://stackoverflow.com/a/18473154/5665870      polarToCartesian: function (centerX, centerY, radius, angleInDegrees) {        var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;        return {          x: centerX + (radius * Math.cos(angleInRadians)),          y: centerY + (radius * Math.sin(angleInRadians))        };      },      //copy from https://stackoverflow.com/a/18473154/5665870      describeArc: function (x, y, radius, startAngle, endAngle){          var start = this.polarToCartesian(x, y, radius, endAngle);          var end = this.polarToCartesian(x, y, radius, startAngle);          var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";          var d = [              "M", start.x, start.y,               "A", radius, radius, 0, largeArcFlag, 0, end.x, end.y          ].join(" ");          return d;             }  }});
.st0{fill:#FFFFFF;}.st1{fill:none;stroke:#B5B5B5;stroke-miterlimit:10;}.st2{fill:none;stroke:#408EFF;stroke-linecap:round;stroke-miterlimit:10;}.st3{fill:#408EFF;}
<script src="https://momentjs.com/downloads/moment.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script><div id="app"><p>{{pathProgess}}</p><svg x="0px" y="0px" viewBox="0 0 90 90">    <rect class="st0" width="90" height="90"/>    <circle class="st1" cx="45" cy="45" r="40"/>    <text class="circle-chart-percent" x="20.91549431" y="40.5" font-size="8">{{time}}</text>    <path class="st2" :d="pathProgess"/>    <circle class="st3" cx="45" cy="5" r="3"/></svg></div>