前言

最近项目有用到圆环进度条,所以也找了一些文章,看看是如何实现的。市面上的文章讲述的实现方式基本都是两个长方形旋转和canvas这两种方式,大家如果有兴趣可以自己去找找,百度一搜一大把😂。


然而我做的项目是小程序(提供demo是vue版本),canvas层级非常高,虽然官方提供了同层canvas,但是同层canvas在官方社区上反馈bug非常多,而且感觉比较重,所以就没考虑。两个长方形旋转的方式我之前也试过,真机上两个长方形交接处会存在差不多1px的间距,一直无法修复,也没法和设计说只能这样是不是。


然后无意之中看到了这样一个demo,感觉打开了新世界,原来圆环进度条还能这样实现啊!!

linear-gradient实现圆环


先看下先看下我们要实现的效果
image.png


上面的图可以拆分为4个dom结构,或许这样看你更清晰
image.png
最底层为深红色的圆
中间红色的小圆,用来遮盖最底层的圆,视觉上形成圆环的效果
黑色的两个小圆,用于实现带圆弧的圆环进度条


首先我们先实现最底部深红色的圆

<div class="progress-radial"></div>
.progress-radial {
    position: relative;
    width: 120rpx;
    height: 120rpx;
    border-radius: 50%;
    background-image: linear-gradient(90deg, #C40006 %, #FECC1C);
    line-height: normal;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1;
}

这时我们会得到这样一个圆
image.png
当然,这肯定不是我们想要的效果,我们并不需要这个 过渡 的效果,所以可以修改一下 linear-gradient 的节点,让节点重合形成清晰的界线

background-image: linear-gradient(90deg, #C40006 50%, #FECC1C 50%, #FECC1C);

这时我们就可以得到一个两种颜色对半分并且没有过渡色的圆
image.png
假设我们把 黄色 作为当前进度,那我们在这个圆上加个遮罩层,不就可以得到一个进度为50%的圆环进度条了吗!

<div class="progress-radial">
    <div class="progress-text"></div>
</div>
.progress-radial {
    position: relative;
    width: 120rpx;
    height: 120rpx;
    border-radius: 50%;
    background-image: linear-gradient(90deg, #C40006 50%, #FECC1C 50%, #FECC1C);
    line-height: normal;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1;
}

.progress-radial .progress-text {
    width: 88rpx;
    height: 88rpx;
    background: #ED1937;
    border-radius: 50%;
}

image.png
虽然。。。我们得到了这个50%的圆环进度条,但是进度不可能一直都是50%呀😭,我们必须根据实际的数据去算进度到底是多少,也就是说,我们必须要让这个进度条动起来。


那我们该怎么让进度条动起来那


假设我们先动一下 linear-gradient 的 angle ,让 angle = 145deg 会得到下面这样的情况
image.png
虽然进度是动了起来,但是进度条的起始点不在 90deg 上,那有没有办法可以实现起始点在 90deg 上,但是进度条还可以动起来呢?


答案肯定是可以的。我们将这个圆想象成上下两个部分,当进度小余50%的时候,上下两部分如下图所示
图一
image.png
图二
image.png
将 图一 盖到 图二 上的话就可以得到一个全红色的圆
image.png

.progress-radial {
    position: relative;
    width: 120px;
    height: 120px;
    border-radius: 50%;
    background-image: linear-gradient(90deg, #C40006 50%, transparent 50%, transparent) , linear-gradient(90deg, #FECC1C 50%, #C40006 50%, #C40006);
    line-height: normal;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1;
    margin: 0 auto
}

然后我们修改 图二 部分的 linear-gradient 的 angle 达到修改进度的效果,比如我们试试将的 angle 改为120deg,其实我们可以想像成把上面 图二 转动了30度后得到以下效果

.progress-radial {
    position: relative;
    width: 120px;
    height: 120px;
    border-radius: 50%;
    background-image: linear-gradient(90deg, #C40006 50%, transparent 50%, transparent) , linear-gradient(120deg, #FECC1C 50%, #C40006 50%, #C40006);
    line-height: normal;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1;
    margin: 0 auto
}

image.png
当大于50%进度的时候, 图二 就转到了 270deg ,就得到了之前的对半切的样式,这时候我们将 linear-gradient 换成大于50%的样式,上下切图分别为
图一
image.png
图二
image.png
还是老方法,将 图一 盖到 图二 上可以得到一个50%进度的圆,然后我们转动 图一

.progress-radial {
    position: relative;
    width: 120px;
    height: 120px;
    border-radius: 50%;
    background-image: linear-gradient(-90deg, #FECC1C 50%, transparent 50%, transparent) , linear-gradient(270deg, #FECC1C 50%, #C40006 50%, #C40006);
    line-height: normal;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1;
    margin: 0 auto
}

然后我们可以修改 图一 部分的 linear-gradient 的 angle 达到修改进度的效果,比如我们试试将的 angle 改为-60deg,可以想像把上面 图一 转动了30度后得到以下效果
image.png
这样我们就实现了一个可以实现任意百分比的圆环,接下来我们就直接根据数据结合上述两种情况完成动态计算进度的圆环把

<template>
  <div id="app">
    <div class="progress-radial" :style="point <= 50 ? 'background-image: linear-gradient(90deg, #C40006 50%, transparent 50%, transparent), linear-gradient('+deg+'deg, #FECC1C 50%, #C40006 50%, #C40006);' : 'background-image: linear-gradient('+deg+'deg, #FECC1C 50%, transparent 50%, transparent), linear-gradient(270deg, #FECC1C 50%, #C40006 50%, #C40006)'">
      <div class="progress-text"></div>
    </div>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      deg: 0,
      point: 0,
    }
  },
  mounted() {
    // 完成的数量,整体的数量
    let { finishCount, totalCount } = {
      finishCount: 1,
      totalCount: 3,
    };
    // 完成数量的百分比,保留2位小数
    let point = Math.floor((finishCount / totalCount) * 10000) / 100;
    let deg = 0;
    // 360度,按百分比每分3.6度,小于50%以90deg为起始,大于50%以-90deg为起始
    if (point <= 50) {
      deg = Math.round(90 + point * 3.6);
    } else {
      deg = Math.round(-90 + (point - 50) * 3.6);
    }
    this.deg = deg
    this.point = point
  },
};

这时我们就完成了修改 finishCount 修改进度的效果
image.png

transform-origin实现头尾圆弧

头尾圆弧也就上上面我们拆分的时候说的黑色圆,起始圆永远都位于最上方,所以只要定位好就可以,结束圆需要计算真实的进度,然后旋转到对于位置。
如何去转这个黑色的小圆,这里我们是用了 transform-origin ,也就是将这个黑色圆的旋转基点定到红色圆的圆心,然后用 rotate 就可以得到一个绕圆环边缘转的黑色小圆了,然后上面我们计算得到的 deg 大于50和小于50的的起始基点不一样,所以我们也需要对这个数值做一下处理。
最终代码和效果图如下

<template>
  <div id="app">
    <input v-model="finishCount" @change="draw" type="number" class="input" />
    <div
      class="progress-radial"
      :style="
        point <= 50
          ? 'background-image: linear-gradient(90deg, #C40006 50%, transparent 50%, transparent), linear-gradient(' +
            deg +
            'deg, #FECC1C 50%, #C40006 50%, #C40006);'
          : 'background-image: linear-gradient(' +
            deg +
            'deg, #FECC1C 50%, transparent 50%, transparent), linear-gradient(270deg, #FECC1C 50%, #C40006 50%, #C40006)'
      "
    >
      <div class="progress-text"></div>
      <div
        class="progress-radial-end"
        :style="'transform: rotate('+(point <= 50 ? deg-90 : 270+deg)+'deg)'"
      ></div>
      <div class="progress-radial-start"></div>
    </div>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      finishCount: 1,
      totalCount: 3,
      deg: 0,
      point: 0,
    };
  },
  mounted() {
    this.draw();
  },
  methods: {
    draw() {
      let point =
        Math.floor((this.finishCount / this.totalCount) * 10000) / 100;
      let deg = 0;
      // 360度,按百分比每分3.6度
      if (point <= 50) {
        deg = Math.round(90 + point * 3.6);
      } else {
        deg = Math.round(-90 + (point - 50) * 3.6);
      }
      this.deg = deg;
      this.point = point;
    },
  },
};
</script>

<style>
.input {
  margin: 30px auto;
  display: block;
}

.progress-radial {
  position: relative;
  width: 120px;
  height: 120px;
  border-radius: 50%;
  background-image: linear-gradient(
      90deg,
      #c40006 50%,
      transparent 50%,
      transparent
    ),
    linear-gradient(90deg, #fecc1c 50%, #c40006 50%, #c40006);
  line-height: normal;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
  margin: 0 auto;
}

.progress-radial-end,
.progress-radial-start {
  width: 16px;
  height: 16px;
  top: 0;
  left: 52rpx;
  border-radius: 50%;
  background: #000;
  position: absolute;
  z-index: 999;
  line-height: normal;
  transform-origin: center 60px;
}

.progress-radial .progress-text {
  width: 88px;
  height: 88px;
  background: #ed1937;
  border-radius: 50%;
  color: #fff;
  font-size: 28px;
  text-align: center;
  line-height: 88px;
  font-weight: bold;
}
</style>

image.png

参考地址文献和demo地址

**





橙年
803 声望6 粉丝