參考來源
《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方法,見源碼的
run和process方法,其中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/
