查看原文
其他

【第1924期】换个思路换个方法,让圆环进度条实现方式得到一点改变

linxz 前端早读课 2021-05-08

前言

来,昨天没动【第1923期】如何用CSS绘制饼图,今天来动的。今日早读文章由@小志,公号:志语自乐授权分享。

正文从这开始~~

环形进度条在几年前就已经开始被大面积使用,记得最早看到是在 https://cssanimation.rocks/watch/ 中写的一个 demo,模拟的是 Apple Watch 中的一个效果,后来延伸出使用在加载进度条中。实现的方式有很多,大概有:

  • 通过 CSS 中的 border-radius 实现;

  • 通过 SVG 实现;

  • 通过 canvas 实现;

主要的实现途径有上面三种,其中包含不少变种方式,原理大致都是相似的,圆角以及遮罩的效果相结合。

最近也不知道为什么,突然想再玩一下这个效果,于是最终做了这个效果的圆环动画。

在做这个动画的时候,原本只是想着完成外圈的那个圆环进度就 ok 了,不过后来想着这个环形的边缘部分并不圆滑,看着不舒服。

所以打算利用之前写的《一个loading让自己明白对于animation还有很多要学》方式再增加一个小圆球,于是增加了一个元素作为运动轨迹引导,也就是前面那个 gif 图中的蓝色线条。这蓝色的线条运动轨迹也是 360° 的旋转,运动时间相等于圆环的运动时间,所以目前看到这个线条是贴合着圆环在运动的,后续只需要添加一个圆形就可以了。

目前暂时的 HTML 结构是:

  1. <div class="circle_box">

  2. <div class="box"></div>

  3. </div>

其中利用了 ::before 和 ::after,因为目前只是一个设想实现的 demo,代码并不完善,后续肯定还是需要进行部分调整的。

在看这部分 demo 的 CSS 代码,稍微分解一下。首先要说明的是,这个圆环其实就是利用两个半圆做 360° 旋转后实现的。

修改其中一部分的半圆的颜色后,可以很明显看到。

中间有一个白色的圆球覆盖了,如果去掉了背景色的话,效果就是:

如果只是单独一个 ::before 元素的话,动画效果是这样的。

反之,使用了 ::after 的这个元素动画,就是从另外一个方向进行改变的。

不过 ::before 和 ::after 这两个元素的动画时间稍微有点差异,延迟一倍的时间,动画整体形成一个时间差,最终组合而成一个完全的圆形动画。

  1. .box:before {

  2. animation: radiusLeft 1s0ms1 forwards linear;

  3. }


  4. .box:after {

  5. animation: radiusRight 2s0ms1 forwards linear;

  6. }

  1. @keyframes radiusLeft {

  2. 0% {

  3. transform: rotate(-180deg)

  4. }

  5. 100% {

  6. transform: rotate(0deg)

  7. }

  8. }


  9. @keyframes radiusRight {

  10. 0%,

  11. 49.9% {

  12. transform: rotate(0deg);

  13. border-color: #fff;

  14. }

  15. 50% {

  16. border-color: #f00;

  17. transform: rotate(-180deg)

  18. }

  19. 100% {

  20. transform: rotate(0deg)

  21. }

  22. }

如果我们改变一下透明度,可以比较明显看到,左边这个半圆会在停顿一下之后出现。

可能会有人好奇为什么在 radiusRight 这个 @keyframes 中 49.9% 部分要设置一个白色的边框色。其实这个只是为了保证在前面一半的运动轨迹中,左边的半圆,也就是 ::after 部分颜色显示跟整体的圆环底色相同,不会出现半圆。这个方式其实也可以通过其他方法来操作,看个人喜好了。

从这个 animation 中可以看到 radiusRight 这个动画时间 animation-duration 比 radiusLeft 要多了 1s ,并没有做动画延迟。因为在 @keyframes 中有差异。

那么这个时候如果两个圆一起动的话,情况就是这样了。

所以,最终的情况大概就是这样。

截图是分别让两个动画运行,有一点时间差,具体的最终效果以实际 demo 为准。

接下来要做的就是稍微美化调整一下,比如中间的圆形部分加个白色背景色,这样就可以得到一个圆环了。顺带可以加点外阴影、内阴影效果,在视觉上感受又不一样了。

还有一点需要说明的是,这里的半圆都是通过 border 来实现的,具体怎么实现就不细说了,反正很简单,像下面这样的代码就可以得到左右各 100px 的矩形。

  1. width: 200px;

  2. height: 200px;

  3. background-color: #ff0;

  4. border-left: 100px solid #f00;

  5. box-sizing: border-box;

然后加上一个 border-radius: 50%; ,也就是左右各 100px 的半圆了。所以,前面说在 @keyframes 里用 border-color 来做另外一种颜色覆盖,其实在这里也可以直接改变 background-color 来实现。具体怎么用就是具体的需求,就目前这个这个情况来看,怎么用都无所谓。

那么圆环的运动完成了,接着就是对这个圆环的头尾加上两个小球来圆环的头尾变得圆滑一点。这样的话,目前的 HTML 结构满足不了,因为如果把小圆球放在外层的 div 标签中,使用 ::before 和 ::after 来做两个小球球的话,就要让外层 div 做旋转运动。当外层有了旋转运动之后,前面做的圆环的运动就会混乱了。毕竟最外层的父级元素都滚动起来了,作为子元素的"圆环"难道不会跟着滚?所以,新增一个 div 标签来满足吧,顺便改一下 className……

  1. <div class="circle_box circle_box__animation">

  2. <div class="ball"></div>

  3. <div class="circle"></div>

  4. </div>

这里的 .ball 就是用来控制小球的。在这个 .ball 中的 ::after 元素跟着做 360° 旋转。而不动的小球就放在父级元素 .circle_box 中。

  1. .ball::after,

  2. .circle_box::before {

  3. content: "";

  4. position: absolute;

  5. top: 50%;

  6. left: 0;

  7. width: 10px;

  8. height: 10px;

  9. overflow: hidden;

  10. transform: translate3d(0, -50%, 0);

  11. background-color: #c0ff6e;

  12. border-radius: 50%;

  13. }

  14. .circle_box::before {

  15. top: 5px;

  16. left: 50%;

  17. transform: translate3d(-50%, -50%, 0);

  18. background-color: #c0ff6e;

  19. z-index: -1;

  20. }

接着就是让 .ball::after 这个小球动起来。

  1. .circle_box__animation .ball {

  2. animation: circle_run 2s 0s 1 forwards linear;

  3. }

  4. @keyframes circle_run {

  5. 0% {

  6. transform: translate3d(0, -50%, 0) rotate(90deg);

  7. }

  8. 100% {

  9. transform: translate3d(0, -50%, 0) rotate(450deg);

  10. }

  11. }

不知道是否有人注意到这里有一个 z-index: -1; 的存在呢。这个存在的意义主要是:

  • 让阴影能够覆盖在圆环上;

  • 让圆环通过层叠到 .circle 下面;

最终的效果大概就是这样了。

如果放大来看,这个圆环的头尾部分也不再是简单的生硬的效果,显得稍微圆润一点了。

分解了这么多步,最终的目的并不只是要做一个圆环的动画,而是想要实现一个进度条的方式。在页面加载的时候,通过数据方式获取到某个数值,形成百分比形式加载,比如这样:

以往制作进度条的时候,大部分我们都是通过修改 width 的方式来改变一个 loading bar 的宽度,形成进度条。而这个圆环的进度条,显然我们是不方便通过 width 来改变了。那么我们是不是可以通过 transform 中的 rotate 值改变来计算呢。

显然,这是可以的。

但是,这样的话,不仅要考虑角度的计算,还有可能涉及到最初这个圆环的 rotate 值是多少。比如,在现在这个 demo 中,使用到的角度值分别有:

  • 90deg

  • -180deg

  • 450deg

  • 0deg

总感觉这样计算起来很繁琐,还要考虑到这些值目前是在 @keyframes 中的,要如何去修改,太麻烦了,不是吗?

其实在 animation 属性中,有一个属性是 animation-iteration-count 是用来计算动画的播放次数的,正常情况下,我们都会选择 1,代表这个动画播放一次,或者使用 infinite 来代表要无限循环播放动画。其实呢,我们可以把这个分成 100 份,比如要播放到一半的时候,就使用 0.5,也就是相当于进度条中的 50% 了。就如前面那张截图显示的是 30%,其实这个时候 animation-iteration-count 的值是 0.3 而已,这样是不是简单很多了呢。

为了模拟这个效果,简单加了点 JS 操作随机数,Math.random(1) 然后再赋值给 animation-iteration-count 就行了。需要注意的是,其中前面半圆要对这个值乘以 2 的操作,毕竟只是滚动了一半。

最后把计算的结果写入到 <style> 中就 ok 了。如果使用的标签元素不是 ::before 以及 ::after 这两个伪元素,而是直接一个空标签的话,那么还可以直接写入到这个空标签的 style 属性值中。总体来说,个人感受还是挺方便的。

有兴趣的可以看看这个具体的 demo,这边我顺带加了一个 type="range" 的 input 标签做为滑动条来控制进度条,不过通过这个滑动条来改变的值就不会有动画效果了,因为 @keyframes 的动画已经加载过了。

demo:http://lab.tianyizone.com/demo/circle-run.html

关于本文 作者:@林小志 原文:https://mp.weixin.qq.com/s/ntDsUkuq4FlMkZnHEn4dYA

为你推荐


【第1503期】不可思议的纯 CSS 滚动进度条效果


【第1537期】Fusion Next 之 Upload 上传组件设计思路


【第1288期】新的 CSS 特性正在改变网页设计

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存