小言_互联网的博客

Vue环形倒计时组件

478人阅读  评论(0)

前言

思考一下这个倒计时可以如何实现呢?

基本知识

以下复制粘贴的张旭鑫大佬的文章。

SVG中有个比较重要的属性分支,名为stroke, 中文软件中称之为“描边”。

  • stroke 表示描边颜色。这很有意思,名字不是stroke-color, 而就是单纯的stroke. 其值,官方称之为”paint“,我就不上梁小丑般翻译了。一般有如下类型值:none, currentColor, <color>,none表示没有颜色,<color>就是我们常规的颜色值。RGBA, HSBA都支持。currentColor略高深,我看了下官方文档,个人理解为:共享父级但不越过SVG元素的XML中color(style中的)值;可以让路径绘制的文字直接继承父标签的color颜色值。

  • stroke-width 表示描边的粗细。

  • stroke-linecap 表示描边端点表现方式。可用值有:butt, round, square, inherit. 表现如下图:

  • stroke-linejoin 表示描边转角的表现方式。可用值有:miter, round, bevel, inherit. 表现如下图:

  • stroke-miterlimit 表示描边相交(锐角)的表现方式。默认大小是4. 什么斜角转斜面的角度损耗之类的意思,值越大,损耗越小。具体干嘛的,我自己也不确定。大家可查查其他资料。

  • stroke-dasharray 表示虚线描边。可选值为:none, <dasharray>, inherit. 其中,none表示不是虚线;<dasharray>为一个逗号或空格分隔的数值列表。表示各个虚线端的长度。可以是固定的长度值,也可以是百分比值;inherit表继承。

  • stroke-dashoffset 表示虚线的起始偏移。可选值为:<percentage>, <length>, inherit. 百分比值,长度值,继承。

  • stroke-opacity 表示描边透明度。默认是1.

基本原理

stroke-dasharray在SVG中表示描边是虚线,两个值,第一个是虚线的宽度,第二个是虚线之间的间距。只需要让间距大于等于圆的周长,然后,虚线的长度 = 百分比值 * 圆的周长就可以了。

// 假设周长是1068, percent是百分比值
circle.setAttribute('stroke-dasharray', 1068 * percent + " 1069");

stroke-dasharray默认的起始位置在右侧,而不是上方,因此需要使用transform逆时针旋转90°。

Vue实现

<template>
  <div class="circle-timer__wrapper">
    <svg
      class="circle-timer__svg"
      viewBox="0 0 100 100"
      xmlns="http://www.w3.org/2000/svg">
      <g class="circle-timer__circle">
        <circle
          class="circle-timer__path-elapsed"
          cx="50"
          cy="50"
          r="45"></circle>
        <path
          :stroke-dasharray="strokeDashArray"
          class="circle-timer__path-remaining"
          :style="{
       color: pathColor }"
          d="
            M 50, 50
            m -45, 0
            a 45,45 0 1,0 90,0
            a 45,45 0 1,0 -90,0
          "></path>
      </g>
    </svg>
    <span class="circle-timer__label">
      {
  {time}}
    </span>
  </div>
</template>

<script>
const FULL_DASH_ARRAY = 283;
const DEFAULT_COLOR = '#FFE2AA';
export default {
    
  name: 'CricleTimer',
  
  props: {
    
    endTime: {
    
      type: Number,
      default: 0
    }, // 结束时间,单位s
    startTime: Number, // 开始时间,单位s
    step: {
    
      type: Number,
      default: -1
    }, // 间隔时间,单位s,倒计时为负数,正计时为正数
    onFinished: Function, // 计时结束事件
    thresholds: Array // 阶段阈值,以及颜色变化。
  },

  data() {
    
    return {
    
      time: this.startTime,
      timer: null,
      timeLimit: Math.abs(this.startTime - this.endTime)
    }
  },

  computed: {
    
    // 路径颜色
    pathColor() {
    
      let result = DEFAULT_COLOR
      if (Array.isArray(this.thresholds)) {
    
        this.thresholds
          .sort((a, b) => a.threshold - b.threshold)
          .some(item => {
    
            if (this.time <= this.timeLimit * item.threshold) {
    
              // 根据当前时间获取距离最近的阈值的颜色
              result = item.color;
              return true;
            }
            return false;
          });
      }
      return result
    },

    // stroke虚线数组
    strokeDashArray() {
    
      // 圆滑过渡
      const rawTimeFraction = this.time / this.timeLimit;
      const timeFraction = rawTimeFraction - (1 / this.timeLimit) * (1 - rawTimeFraction);
      return `${
      (timeFraction * FULL_DASH_ARRAY).toFixed(
          0,
        )} ${
      FULL_DASH_ARRAY}`
    }
  },

  mounted() {
    
    this.timer = setInterval(() => {
    
      // step>0正计时 和 step<0倒计时
      if (
        (this.step < 0 && this.time <= this.endTime) ||
        (this.step > 0 && this.time >= this.endTime)
      ) {
    
        this.onFinished?.();
        clearInterval(this.timer);
      } else {
    
        const cur = this.time + this.step;
        this.time = cur <= 0 ? 0 : cur;
      }
    }, Math.abs(this.step) * 1000);
  },

  beforeDestroye() {
    
    clearInterval(this.timer)
  }
}
</script>

<style lang="less">
.circle-timer {
    
  &__wrapper {
    
    position: relative;
    width: 42px;
    height: 42px;
    margin: auto;
    text-align: center;
  }

  &__svg {
    
    // -1逆时针 1顺时针
    transform: scaleX(1);
  }

  &__circle {
    
    fill: none;
    stroke: none;
  }

  &__path-elapsed {
    
    stroke-width: 8px;
    stroke: rgba(255, 255, 255, 0.2);
  }

  &__path-remaining {
    
    stroke-width: 8px;
    stroke-linecap: round;
    transform: rotate(90deg);
    transform-origin: center;
    transition: 1s linear all;
    fill-rule: nonzero;
    stroke: currentColor;
  }

  &__label {
    
    position: absolute;
    width: 42px;
    height: 42px;
    top: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
  }
}
</style>

使用

<template>
  <CricleTimer
    :start-time="10"
    :step="-1"
    :thresholds="[
      { color: '#FF9370', threshold: 0.5 },
      { color: '#FF1100', threshold: 0.25 }
    ]"
  />
</template>

<script>
import CricleTimer from '@/components/CircleTimer.vue'

export default {
    
  components: {
    
    CricleTimer
  }
}
</script>

参考文档

寥寥数行SVG实现圆环loading或倒计时效果 « 张鑫旭-鑫空间-鑫生活
纯CSS实现帅气的SVG路径描边动画效果 « 张鑫旭-鑫空间-鑫生活
How to Create an Animated Countdown Timer With HTML, CSS and JavaScript | CSS-Tricks


转载:https://blog.csdn.net/sinat_36521655/article/details/115742968
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场