CSS制作环形进度条

时间:2024-11-18 08:33:19

参考来源

Radial progress indicator using CSS》,该文核心是用纯CSS来做一个环形的进度条。纯css的意思就是连百分比这种数字,都是css生成的。文章作者采取的方式是生成100个span标签,然后为这100个标签生成100段css代码(用less生成,代码量倒不大,只是生成的代码量会很大),不知道有没有更NB更省资源的css方案。
而我的需求很简单,只需要学习怎么画环进进度条即可,进度掌控当然得由js来通知(比如下载进度,或者音乐播放进度)
对E文没有恐惧感的话,建议直接看原作者的文,我这不是全文翻译,而是自己的练习。

注:为了方便,我只对chrome写了样式,所以如果要运行我在jsfiddle里面写的例子,最好选webkit系的浏览器

起步

原作者讲解得很详细,我们直接跳过吧,自己学习css3关于动画的部分,总之,先成功一个例子再说:

html

<div class="radial-progress">
<div class="circle">
<div class="mask">
<div class="fill"></div>
</div>
</div>
</div>

一个容器,一个一环形元素,再加一个mask,和fill,后面介绍

css

为了直接在jsfiddle上使用,我也用less吧,毕竟只要一句js声明即可客户端解析(插一句,工程项目建议用sass,因为有compass这个库,大大减轻工作量)

@size:120px;
@bgcolor:#ddd;
@bgcolor1:#cc7;
@duration:1s;
*{margin:0;padding:0;}
.radial-progress{
margin:30px 50px;
position: relative;
.comm(){
width:@size;
height:@size;
position: absolute;
top:0;
left:0;
border-radius: 50%;
}
.circle{
.comm;
background: @bgcolor;
.mask{
clip:rect(0,@size,@size,@size/2);
.comm;
}
.fill{
background: @bgcolor1;
clip:rect(0,@size/2,@size,0);
transition:-webkit-transform @duration;.comm;}
}
}

js

$('head style[type="text/css"]').attr('type', 'text/less');
less.refreshStyles();
$(function(){
var m=$(".fill"),el=$("#deg").val(60),cur=$("#cur");
$("#start").click(function(){
var deg=el.val()||60;
m.css("transform","rotate("+deg+"deg)");
cur.text(deg);
el.val(Math.ceil(Math.random()*180));
});
$("#start").click();
});

讲解

  • 要想弧形运动,首先想到了css3的rotate,并且rotate的旋转原点正好是中心,不需要额外设置,所以我们选择了这个属性。
  • 其次,进度条从无到有,显然不能像直线进度条一样,设置宽度和高度即可,我们只能选择一个层从另一个层的遮挡中逐渐出现这种方案,这就是我们取名叫mask和fill的原因.
  • 上面就是我们的实现:把进度条所属的块只显示左半边,而盛放进度条用的容器,却只显示右半边,(只显示半边是怎么做到的?学习CSS3 clip),这样,我们看不到任何进度条所属的色块,因为它压根就没出现在容器范围内。
  • 然后我们让左边的进度条色块进行旋转,一旦旋转到右侧,色块自然就能在右侧看到了(容器一直在右侧,但是无底色,只有进度条有底色,所以核心就是让进度条色块通过旋转,进入到容器所在的位置内
  • 此图表示左边色块大概转了一百多度转到了右边
  • 最后,加上50%的弧度,圆形就出现了
  • 看看效果:http://jsfiddle.net/walker/smMzz/,饼图是制作成功了

360度

上面的例子做完后,你应该发现这种转法,最多只能转180度啊!好吧,于是我们如法泡制,画一个左边的容j器和右边的色块:

html

<div class="radial-progress">
<div class="circle">
<div class="mask left">
<div class="fill"></div>
</div>
<div class="mask right">
<div class="fill"></div>
</div>
</div>
</div>

css

@size:120px;
@bgcolor:#ddd;
@bgcolor1:#cc7;
@duration:1s;
*{margin:0;padding:0;}
.radial-progress{margin:30px 50px; position: relative;
.comm(){
width:@size;height:@size;position: absolute;top:0;left:0;border-radius: 50%;
}
.circle{
background: @bgcolor;.comm;
.mask,.fill{.comm;}
.fill{
background: @bgcolor1;
transition:-webkit-transform @duration;
}
.left{
clip:rect(0,@size/2,@size,0);
.fill{clip:rect(0,@size,@size,@size/2);}
}
.right{
clip:rect(0,@size,@size,@size/2);
.fill{clip:rect(0,@size/2,@size,0);}
}
}
}

js不变,直接运行http://jsfiddle.net/walker/smMzz/1/

显然不是我们要的效果

改进

这时作者做了大胆的改动,别的进度条方案我还没来得及研究,总之他这种是非常“别扭”的,管它呢,先实现,后面的就都不贴代码了,每一节后面都有我贴的jsfiddle的地址,可以直接去看源码。

  • 首先,去掉了“左,右”蒙板的概念,而是把两个蒙板都并列摆在右侧(一个叫half,一个叫full),蒙板包含的色块仍然叫fill,这样js一次就同时旋转了两个色块。
  • 这样的结果肯定是没有任何变化啊!因为两个层仍然是叠在一起的。所以,作者又让.full这个层整个蒙板也旋转同样的角度!【注意】,此时两个蒙板其实已经不重合了。这样,本来两个重叠的色块,因为某一个容器继续旋转了同样的角度,比如30度,视觉上就出现了60度范围的色块!
  • 所以说别扭,是别扭在,我们封装这种进度条,其实最大只需要旋转180度。
  • 见结果:http://jsfiddle.net/walker/smMzz/2/
  • 最后,因为事实上是两个色块拼接的,拼接处有一条细线(只有在动画进行的过程中才有),仔细看上面链接的演示。于是作者继续给出解决方案:在half层里面添加一个色块,class也是fill,所以也会被js控制进行旋转,但是这个fix的fill是直接按2倍角度旋转的,这样在旋转的过程中,因为速度的不同,它就挡住了那条白线(这里才需要对js进行一点修改,2倍旋转fix层)。
  • 如果对用户体验没这么关心(怎么可能!),其实这件事可以不做的。修改后见:http://jsfiddle.net/walker/smMzz/3/

把饼图改成环图

至此已经大功告成,我们中间再添加一个跟底色一样的div把它变圆不就可以了吗?
对的,顺便还加了点内阴影外阴影,这样就有3D甜甜圈的效果了:http://jsfiddle.net/walker/smMzz/4/

如果要看代码,就注意一下,这一步只是添加了一个.inset和一个.shadow,对应的css看源码。

我们这里就不要3D了,简化一下,把3D啊,阴影啊,都去掉:http://jsfiddle.net/walker/smMzz/5/

模拟进度条

至此,我参考的老外原文已经和我下面的东西不沾边了,感兴趣他怎么用纯css来实现动态进度条的可继续在原文观看,我这里基本上是js部分了,目的是让进度条响应当前进度

  • 我们添加一个div来放数字,位置就在.inset里面,以百分号表示
  • 我们封装一个js方法,见源码的runprocess方法,其中process其实就是把前面onclick的内容给提取了出来,只需要传入一个100以内的数字
  • 而run方法则包含了一定业务逻辑此处的逻辑是传入一个开始进度和结束进度,我就每1%调用一次进度条(真实的业务逻辑一般为:我监测下载进度,或歌曲播放进度,一旦有变化,就通知这个函数更改UI)
  • 讲解得比较抽象,运行一下结果再看代码:http://jsfiddle.net/walker/smMzz/6/
  • 补充一句,前面之所以要有1秒的动画切换时间,纯粹为了好看(各种js生成的图表,也是为了展示这个生成过程,好看),而我们用来做“被通知”的进度条的时候,就没必要了,比如你现在在89%,要跳到90%,那就直接转到90%即可,而不是需要这1S的转场时间。因为事实上我们已经有这个时间了:下载的时间,歌曲播放的时间。所以我把transition去掉了。

模拟播放器

上面的例子是模拟下载或者上传进度条。什么意思?不管是上传,还是下载,进度只是一个“状态”,你不能手动更改这个状态,而播放器则不同,你更改这进度条的百分比,应该能影响歌曲或影片从哪个时间点开始播放,因此,我们需要响应点击事件,同时还要会计算点击位置的角度:

      • 我们选择.circle作为点击事件的响应对象,因为不受.mask.fill元素是否可见的影响。并把其鼠标状态改为手形
      • 一旦进度条出现,就会覆盖在.circle上面,所以我们又要把.mask.fill这两个层设为鼠标穿透(用pointer-events:none实现),以免点击不到.circle元素
      • 计算角度的函数见源码,我也是网上搜的,有效。
      • 角度转化成百分比,调用run函数,it works
      • 最后,干脆替换一个播放器按钮
      • 见示例: http://jsfiddle.net/walker/smMzz/7/