一个环形进度条渐变色动画细节点环形精度条

  • A+
所属分类:未分类
摘要

最近在开发的时候遇到了一个环形进度条的需求,设计师希望这个进度条是渐变色的,并且能有对应的动画。具体效果如图因为git图的缘故所以看起来有点卡

最近,在开发过程中,遇到了一个圆形进度条的需求。 设计者希望进度条有渐变颜色,并且能有相应的动画。具体效果如图

由于是git镜像,看起来有点卡,但是实测帧率还是可以稳定在50到60之间。并且封装成Vue组件,只需要传入相应的参数就可以快速生成内容,如果你有类似的需求,可以参考以下链接 aboyl 的 github switch 分支到 svg-circle-progress 查看对应的源码和相关文档

我们先来解释一下总体思路:我们需要什么?

圆形进度条渐变动画

细节点圆形精度条的起点和终点均为矩形

我们先来实现一下循环进度条。实现思路很简单。 使用svg画两个圆圈,一个圆圈作为背景色,另一个圆圈作为进度条。 此时只需绘制圆弧即可。

如何画一个圆

参考svg文档,我们可以知道以下代码

  

画六边形的简易画法_画六边形的方法_svg画六边形

疗效如图

我们得到一个黑色的圆环,其中 r 是直径 cx,cy 是 svg 中的坐标。 笔划是颜色 笔划宽度 是笔划的长度 填充是 none 表示不填充,否则我们会看到整个圆而不是圆环

然后我们需要画圆弧来画圆弧。 我们可以使用Stroke-dasharray。 他的初衷是用虚线和实线交替画一条线段。 我们将参数设置为“弦长,最大值”,这样显示效果比实线更好。 空的部分很长,所以我们不会看到第二段的虚线。 对于我们需要的圆领,我们可以将strike-linecap设置为round,最终效果如图

代码如下所示

    <circle
      :r="50"
      :cx="100"
      :cy="100"
      :stroke="'red'"
      :stroke-width="10"
      fill="none" 
    />
    <circle
      :r="50"
      :cx="100"
      :cy="100"
      :stroke="'yellow'"
      :stroke-dasharray="`100,100000`"
      :stroke-width="10"
      fill="none"
      stroke-linecap="round"
    />
  

此时我们观察到起始方向是在右侧中间,所以我们旋转,给第二个圆加上旋转

      transform="rotate(-90)"
      transform-origin="center"

svg画六边形_画六边形的方法_画六边形的简易画法

因为我们需要将其封装成一个组件,所以他应该接收

进度背景圆弧的颜色、内圆的直径、圆弧的长度以及svg的宽度和高度、外圆的直径和弦长

这种价值应该由我们来估计。 事实上,为了使用的方便,我们应该给出一些默认值。 组件代码如下


  


export default {
  name: 'Progress',
  props: {
    progress: {
      type: Number,
      required: true,
    },
    progressOption: {
      type: Object,
      default: () => { },
    },
  },
  data () {
    return {
    }
  },
  computed: {
    arcLength () {
      let circleLength = Math.floor(2 * Math.PI * this.option.radius)
      let progressLength = this.progress * circleLength
      return `${progressLength},100000000`
    },
    option () {
      // 所有进度条的可配置项
      let baseOption = {
        radius: 100,
        strokeWidth: 20,
        backColor: 'red',
        progressColor: 'yellow',
      }
      Object.assign(baseOption, this.progressOption)
      // 中心位置自动生成
      baseOption.cy = baseOption.cx = baseOption.radius + baseOption.strokeWidth
      baseOption.size = (baseOption.radius + baseOption.strokeWidth) * 2
      return baseOption
    },
  },
}

虽然现在可以改让人吐槽的配色了,但已经可以用了~

拿出来我们来实现渐变色

第一个想法其实就是寻找如何在svg中实现渐变颜色。 我一开始也搜索过,最后写下了下面的代码

    
      <linearGradient id="gradient">
        <stop
          offset="0%"
          style="stop-color: red;"
        />
        <stop
          offset="100%"
          style="stop-color: yellow"
        />
      
    
    <circle
      :r="option.radius"
      :cx="option.cx"
      :cy="option.cy"
      :stroke="option.backColor"
      :stroke-width="option.strokeWidth"
      fill="none"
    />
    <circle
      :r="option.radius"
      :cx="option.cx"
      :cy="option.cy"
      :stroke="'url(#gradient)'"
      :stroke-dasharray="arcLength"
      :stroke-width="option.strokeWidth"
      fill="none"
      transform="rotate(-90)"
      transform-origin="center"
      stroke-linecap="round"
    />
  

疗效如图

画六边形的简易画法_画六边形的方法_svg画六边形

为什么和我们预期的疗效不一样呢? 我们期望的是从顶部顺时针绘制一条圆弧,底部颜色为蓝色,末端为白色。 为什么会这样呢? 因为虽然我们的观点是错误的。 如果我们不做其他处理,那么简单地给一个圆加上一个渐变会是什么样子呢?

    
      <linearGradient id="gradient">
        <stop
          offset="0%"
          style="stop-color: green;"
        />
        <stop
          offset="100%"
          style="stop-color: yellow"
        />
      
    
    
    <circle
      :r="option.radius"
      :cx="option.cx"
      :cy="option.cy"
      :stroke="'url(#gradient)'"
      :stroke-width="option.strokeWidth"
    />
  

如图所示

可以看出,线性渐变是从左到左的。 刚才位置不对是因为轮换的激励

这样我们就可以推翻上面我们不靠谱的猜想,回去继续思考如何实现梯度了。 至于有没有办法利用svg渐变元素实现渐变交互svg画六边形,由于CSS可以做出很多带有渐变的游戏,所以我不能保证没有,但我觉得可能会比较麻烦实施想法,所以我改变想法去实施。

我们只需要自动估计梯度,也就是说,我们只需要实现算法来估计从颜色a到颜色b的渐变颜色,并使用不同弦长和不同颜色的圆进行重叠,这样我们就可以模拟渐变的实现需要注意的是,渐变的实现并不像有​​时认为的那样,是从000000累加到ffffff,而是需要先转成rgb再进行估计。 通过搜索引擎,我们可以找到一些实现良好的算法,具体原理就不详细描述了。 上面的文章也实现了rgb转16的补码的算法,但是核心梯度算法大致如下。 如有需要可以参考其他算法。

      function gradientColor (startRGB, endRGB, step) {
        let startR = startRGB[0]
        let startG = startRGB[1]
        let startB = startRGB[2]
        let endR = endRGB[0]
        let endG = endRGB[1]
        let endB = endRGB[2]
        let sR = (endR - startR) / step // 总差值
        let sG = (endG - startG) / step
        let sB = (endB - startB) / step
        var colorArr = []
        for (var i = 0; i < step; i++) {
          let color = 'rgb(' + parseInt((sR * i + startR)) + ',' + parseInt((sG * i + startG)) + ',' + parseInt((sB * i + startB)) + ')'
          colorArr.push(color)
        }
        return colorArr
      }

svg画六边形_画六边形的简易画法_画六边形的方法

可以看到rgb颜色的三位分别步进,达到了渐变的效果

接下来的问题是如何估计步数,但是根据我们上面的分析,渐变颜色应该对应于弧形。 经过一些测试,当步数为100时,肉眼不太能够辨别渐变颜色的存在。 (ps:本来在写这篇文章之前,我的想法是做一些估算,在进度高的情况下,步数会比进度低的少,但是讲到这里,我突然想到,如果太高的话,就会造成语无伦次……所以我可以回来修复一下计划。事实证明,如果你认真总结的话,你会发现更多的发现)

我们将步数设置为100,并将原始弦长分成100等分,得到一个链表。 我们根据上面生成的字段,使用v-for生成原始svg中的circl元素。 代码如下


  


export default {
  name: 'Progress',
  props: {
    progress: {
      type: Number,
      required: true,
    },
    progressOption: {
      type: Object,
      default: () => { },
    },
  },
  computed: {
    arcArr () {
      let circleLength = Math.floor(2 * Math.PI * this.option.radius)
      let progressLength = this.progress * circleLength
      const step = 100 // 设置到100则已经比较难看出来颜色断层
      const gradientColor = (startRGB, endRGB, step) => {
        let startR = startRGB[0]
        let startG = startRGB[1]
        let startB = startRGB[2]
        let endR = endRGB[0]
        let endG = endRGB[1]
        let endB = endRGB[2]
        let sR = (endR - startR) / step // 总差值
        let sG = (endG - startG) / step
        let sB = (endB - startB) / step
        let colorArr = []
        for (let i = 0; i < step; i++) {
          let color = `rgb(${sR * i + startR},${sG * i + startG},${sB * i + startB})`
          colorArr.push(color)
        }
        return colorArr
      }
      let colorArr = gradientColor(this.option.startColor, this.option.endColor, step)
      // 计算每个步进中的弧长
      let arcLengthArr = colorArr.map((color, index) => ({
        arcLength: `${index * (progressLength / 100)},100000000`,
        color: color
      }))
      arcLengthArr.reverse()
      return arcLengthArr
    },
    option () {
      // 所有进度条的可配置项
      let baseOption = {
        radius: 100,
        strokeWidth: 20,
        backColor: '#E6E6E6',
        startColor: [249, 221, 180],
        endColor: [238, 171, 86], // 用于渐变色的开始
      }
      Object.assign(baseOption, this.progressOption)
      // 中心位置自动生成
      baseOption.cy = baseOption.cx = baseOption.radius + baseOption.strokeWidth
      baseOption.size = (baseOption.radius + baseOption.strokeWidth) * 2
      return baseOption
    },
  },
}

需要注意的是,我们最后把生成的字段颠倒了,不然弧线最长的弧线会挂在最后,导致我们看不到如图所示的渐变效果的效果

这里我稍微调整了一下颜色svg画六边形,使其符合我们的预期,最后给它添加了动画效果

这部分并不是一个很复杂的事情。 需要注意的是,我们需要去掉圆上的Stroke-DashArray,防止一开始渲染字符串长度然后立即消失进入动画的效果,即使影响不大,但还是需要注意,代码如下

    <circle
      v-for="(item, index) in arcArr"
      :key="index"
      :r="option.radius"
      :cx="option.cx"
      :cy="option.cy"
      :stroke="item.color"
      :stroke-width="option.strokeWidth"
      fill="none"
      transform="rotate(-90)"
      transform-origin="center"
      stroke-linecap="round"
    >
      <animate
        :to="item.arcLength"
        begin="0s"
        :dur="option.durtion"
        from="0,1000000"
        attributeName="stroke-dasharray"
        fill="freeze"
      />
    

画六边形的方法_svg画六边形_画六边形的简易画法

总结

确实,学了东西之后,需要做一些输出,才能达到真正的学习。 虽然一开始整个组件设计和现在差别很大,中间也走了很多弯路,从一开始就误解了svg的渐变。 是的,虽然当我改变主意的时候,我以为是svg渐变作用在圆弧上时顺时针画了一个圆,并在50%的时候从结束颜色切换到开始颜色,达到了循环渐变,直到写下这篇文章我才意识到自己的错误。 比如一开始加了渐变效果之后,步长的估计出现了问题,从而引发了精度的概念,我只能达到 0.01 的进度,而一旦切换到高精度,会导致动画特别卡。 后来,如果你改变主意,就会清楚得多。 需要补充以下内容

由于重点是梯度,所以对接受的参数没有太多要求。 查看附加的参考文章并更改它以使 startColor 和 endColor 接受正常颜色值。 这里只制作了两种渐变颜色。 如果需要多个梯度,我觉得修改算法也不会很难。 对于步数为100,我这里做了一些测试。 在我测试的颜色值下,我认为20的数量不会有太大区别,但性能上100的情况和20的情况似乎没有太大区别,所以没有进一步修正,但它变成了参数默认值的另一种实现方法:

果然,学无止境。 当我在网上搜索文章时,突然发现了一篇漏网之鱼的张大师的文章。 它只使用了两个圆形元素就达到了渐变的效果。 所以对于这个进度条我也借鉴了它的一些帮助。 进一步优化参考链接

张鑫旭的渐变进度条实现了,但是确定最终的尾部颜色是设定的结束颜色,不过这个也可以作为补充。 具体效果如图

可以看到一侧末端的颜色略带紫色。 具体实现方法可以看代码。 我个人认为需要注意的点实现方法本质上是两个圆的叠加,然后旋转得到colorA到colorB的中间值。 colorC,那么第一个是从colorA到colorC,第二个是从colorB到colorC,并从上到下旋转,这样叠加看起来就像从colorA到colorC再到colorB。 注意动画的实现,需要根据比例栏切割动画的时长,以免导致最后平滑动画在经过顶部时速度突然下降,注意注意动画的无缝衔接

具体参考代码如下


  


export default {
  name: 'Progress2',
  props: {
    progress: {
      type: Number,
      required: true,
    },
    progressOption: {
      type: Object,
      default: () => { },
    },
  },
  computed: {
    arcOption () {
      let arcConfig = {}
      let circleLength = Math.floor(2 * Math.PI * this.option.radius)
      // 如果此时小于0.5 则只需要显示最外层的圆弧 里面的圆弧不需要画了
      // 时间计算 因为第二段的长度不见得等于第一段 所以不能平分时间 不然会导致第二端的速度出现骤降
      // 因此需要按照比例进行时间计算
      if (this.progress < 0.5) {
        arcConfig.outArcLength = this.progress * circleLength
        arcConfig.outDurtion = this.option.durtion // 为初始设置的动画值
        arcConfig.innerArcLength = 0
        arcConfig.innerInitArcLength = 0 // 为动画做准备
        arcConfig.innerDurtion = 0
      } else {
        const time = this.option.durtion.split('s')[0]
        arcConfig.outArcLength = 0.5 * circleLength
        arcConfig.outDurtion = (0.5 / this.progress) * time + 's' // 
        arcConfig.innerArcLength = this.progress * circleLength
        arcConfig.innerInitArcLength = 0.5 * circleLength // 为动画做准备 此时从中间开始
        arcConfig.innerDurtion = ((this.progress - 0.5) / this.progress) * time + 's' // 为动画做准备 此时从中间开始
      }
      const tansfromColor = arr => `rgb(${arr[0]},${arr[1]},${arr[2]})`
      arcConfig.outArcStartColor = tansfromColor(this.option.startColor)
      arcConfig.outArcEndColor = tansfromColor(this.option.startColor.map((color, index) => color + (this.option.endColor[index] - color) / 2))
      arcConfig.innerArcStartColor = tansfromColor(this.option.endColor)
      arcConfig.innerArcEndColor = tansfromColor(this.option.startColor.map((color, index) => color + (this.option.endColor[index] - color) / 2))
      return arcConfig
    },
    option () {
      // 所有进度条的可配置项
      let baseOption = {
        radius: 100,
        strokeWidth: 20,
        backColor: '#E6E6E6',
        startColor: [249, 221, 180],
        endColor: [238, 171, 86],
        durtion: '1s',
        step: 100,
      }
      Object.assign(baseOption, this.progressOption)
      // 中心位置自动生成
      baseOption.cy = baseOption.cx = baseOption.radius + baseOption.strokeWidth
      baseOption.size = (baseOption.radius + baseOption.strokeWidth) * 2
      return baseOption
    },
  },
}

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: