前言
最近项目有用到圆环进度条,所以也找了一些文章,看看是如何实现的。市面上的文章讲述的实现方式基本都是两个长方形旋转和canvas这两种方式,大家如果有兴趣可以自己去找找,百度一搜一大把?。
然而我做的项目是小程序(提供demo是vue版本),canvas层级非常高,虽然官方提供了同层canvas,但是同层canvas在官方社区上反馈bug非常多,而且感觉比较重,所以就没考虑。两个长方形旋转的方式我之前也试过,真机上两个长方形交接处会存在差不多1px的间距,一直无法修复,也没法和设计说只能这样是不是。
然后无意之中看到了这样一个demo,感觉打开了新世界,原来圆环进度条还能这样实现啊!!
linear-gradient实现圆环
先看下先看下我们要实现的效果
上面的图可以拆分为4个dom结构,或许这样看你更清晰
首先我们先实现最底部深红色的圆
<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; }
这时我们会得到这样一个圆
background-image: linear-gradient(90deg, #C40006 50%, #FECC1C 50%, #FECC1C);
这时我们就可以得到一个两种颜色对半分并且没有过渡色的圆
<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%; }
那我们该怎么让进度条动起来那
假设我们先动一下 linear-gradient 的 angle ,让 angle = 145deg 会得到下面这样的情况
答案肯定是可以的。我们将这个圆想象成上下两个部分,当进度小余50%的时候,上下两部分如下图所示
图一
.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 }
.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度后得到以下效果
<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 修改进度的效果
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>
参考地址文献和demo地址
**