【问题标题】:How to create a circle progress inside a Vue component如何在 Vue 组件内创建圆形进度
【发布时间】:2019-02-03 15:32:39
【问题描述】:

我正在使用 Vue 构建的应用程序中创建一个组件。这个组件是一个倒计时,范围从X分钟到00:00

我知道可以为svg 制作动画以达到预期的效果,但我没有必要的知识。我从未使用过任何svg 库。

我需要在我的进度组件中创建以下动画:

动画需要顺着路径顺着天气走。路径节点应根据时间插入/更新。

这是我实际的倒计时组件:

var app = new Vue({
  el: '#app',
  data: {
    date: moment(2 * 60 * 1000)
  },
  computed: {
    time: function(){
      return this.date.format('mm:ss');
    }
  },
  mounted: function(){
  	var timer = setInterval(() => {
      this.date = moment(this.date.subtract(1, 'seconds'));
        
      if(this.date.diff(moment(0)) === 0){
        clearInterval(timer);
        
        alert('Done!');
      }
    }, 1000);
  }
});
<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">{{ time }}</div>

这是进度圈的 svg:

<svg x="0px" y="0px" viewBox="0 0 90 90">
    <style type="text/css">
        .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;}
    </style>
    <rect class="st0" width="90" height="90"/>
    <circle class="st1" cx="45" cy="45" r="40"/>
    <path class="st2" d="M45,5c22.1,0,40,17.9,40,40S67.1,85,45,85S5,67.1,5,45S22.9,5,45,5"/>
    <circle class="st3" cx="45" cy="5" r="3"/>
</svg>

我怎样才能达到预期的效果?

欢迎所有帮助。

【问题讨论】:

  • @Sphinx setInterval 是必需的,因为我的进度圈使用时间而不是百分比。此外,动画并不完美,除了路径末端没有小圆圈。

标签: javascript css animation svg vue.js


【解决方案1】:

按照您的模板,一种解决方案是将路径预定义为一个数组(每个路径节点是数组的一个元素)。然后将路径节点推送到每个间隔的当前进度路径。

如下演示:

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>

或者您可以使用Answered at another question的方法,实时计算路径。

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>

【讨论】:

  • 酷,不过圈子要逐渐闭合,伴随着setInterval返回的时间。
  • 是否有可能让圆圈和蓝色笔划沿着平行于总时间的路径? 0% = 2:00,100% = 00:00。过渡应该是平稳的。
  • 路径节点应该根据时间插入/更新
  • @CaioKawasaki 查看第二个演示,它将是您所需要的。
【解决方案2】:

您需要熟悉 SVG 形状,尤其是 &lt;path&gt; 才能制作弧线。

这是一个例子:

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>

至于动画,你只需要使用 JavaScript 来更改 value 属性,例如使用 setInterval 或其他方式。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-19
    • 1970-01-01
    • 2021-04-27
    • 2014-05-17
    • 1970-01-01
    • 2016-06-03
    相关资源
    最近更新 更多