最近團隊的童鞋接到了一個有關環形進度條的需求,想要還原一個native的沿環軌跡漸變進度條的效果,看到這個效果的時候,筆者陷入了沉思。。css
環形進度條的效果,最早想到的就是使用CSS利用兩個半圓的hack來模擬實現的:android
<div class='circle-container'> <div class="circle-item"> <div class="circle-left-wrap"> <div class="left"></div> </div> <div class="circle-right-wrap"> <div class="right" style="transform: rotate(70deg)"></div> </div> <div class='mask'></div> </div> </div> <div class='circle-container'> <div class="circle-item"> <div class="circle-left-wrap"> <div class="left" style="transform: rotate(70deg)"></div> </div> <div class="circle-right-wrap"> <div class="right" style="transform: rotate(180deg)"></div> </div> <div class='mask'></div> </div> </div> <style> .circle-container{ position: relative; float: left; width: 120px; height: 120px; } .circle-item{ position: absolute; left: 10px; top: 10px; width: 100px; height: 100px; border-radius: 50%; background-color: #59d; } .circle-left-wrap, .circle-right-wrap{ position: absolute; left: 0; top: 0; width: 50px; height: 100px; overflow: hidden; } .circle-right-wrap{ left: 50px; } .left, .right { position: absolute; top: 0; left: 0; width: 100px; height: 100px; border-radius: 50%; background: #ddd; } .left{ clip: rect(0, 50px, auto, 0); } .right{ clip: rect(0, auto, auto, 50px); left: -50px; } .mask{ position: absolute; top: 5px; left: 5px; width: 90px; height: 90px; border-radius: 50%; background-color: #fff; } </style>
代碼如上所示,實現起來並不複雜,主要是利用遮擋關係和clip屬性,具體效果以下:web
其中值得一提的是,雖然這種實現很巧妙,可是:canvas
一、使用上兩個半圓各控制通常的進度,須要js中間作一次轉換,操做起來並不算太方便;api
二、實現上因爲自己的限制,雖然能夠實現帶漸變的進度條(只須要簡單修改最外層容器背景色,效果見下圖),可是因爲自己遮擋關係的實現機制,並不能實現「純透明」的無進度部分;app
三、並且css的實現上彷佛並不支持沿環形的漸變;svg
.circle-item{ position: absolute; left: 10px; top: 10px; width: 100px; height: 100px; border-radius: 50%; background: linear-gradient(to top left, #f63, yellow); }
基於以上緣由,筆者嘗試了一些其餘的實現方式,先進入筆者視野的就是svg,由於工做中其實用得並不算多,也正常趁這次機會實踐一下:性能
<style> svg{ -webkit-mask-image: linear-gradient(to top left, rgba(0,0,0,0), rgba(0,0,0,1)); } svg:last-of-type{ position: absolute; top: 0; left: 0; -webkit-mask-image: linear-gradient(to top left, rgba(0,0,0,1), rgba(0,0,0,0)); } </style> <div class='container'> <i class="circle"></i> <svg width="440" height="440" viewbox="0 0 440 440"> <circle cx="220" cy="220" r="195" stroke-width="50" stroke="yellow" fill="none" transform="matrix(0,-1,1,0,0,440)" stroke-dasharray="700 1069"></circle> </svg> <svg width="440" height="440" viewbox="0 0 440 440"> <circle cx="220" cy="220" r="195" stroke-width="50" stroke="#f63" fill="none" transform="matrix(0,-1,1,0,0,440)" stroke-dasharray="700 1069"></circle> </svg> </div>
因爲沒有采用以前的「遮擋hack」的方式實現,因此實現能徹底沒有css實現的不能實現部分透明的缺陷,並且進度條的控制可使用stroke-array很方便的控制。看上去好像是最好的實現呢?確實每種方案都有優劣,svg也不是全能:spa
一、因爲svg漸變仍然是做用於標籤主體的,彷佛並不能做用於stroke(目前筆者並無查到相關的資料,若是錯誤,煩請留言),那麼環的漸變要如何實現呢?筆者使用了mask-image,而後經過疊加兩個一樣進度的circle標籤,改變它們alpha通道上的漸變防線,實現的和以前CSS同樣的漸變效果;3d
二、svg自己實踐上也有一點小問題,固然這是其自己機制所致,並不算是實現的缺陷:
1)svg 的witdh和height與viewbox第3、第四個參數存在着對應關係(這就和咱們以前提到的縮放的關係是同樣的,請特別注意);
2)circle的cx、cy其實是計算邊框的,也便是若是圓範圍爲440*440,那麼中點就在(220,220);
3)circle的半徑r在計算時指的是從圓心到邊框中心的距離,以上述代碼爲例,邊框爲50的circle,它的半徑應該是220 - 50 / 2 = 195;
4)因爲circle的起點並非一般的圖形的最上部而是最右側(也就是3點鐘方向),因此還須要作一點的transform;
固然,撇開一些svg的使用特性,svg的這套方案仍是蠻不錯的,不過因爲mask-image和svg自己的兼容性上有一些問題,使用的時候仍是須要謹慎對待。
既然說到了svg,那就不得不提提canvas的實現了,筆者雖然本身也簡單了實現了一下,不過看到codepen上面一個很酷炫的實例。。就不放上本身的獻醜了:
固然這個例子並非一個環形進度條的例子(從原名就能看出來XD),還有不少多餘的粒子效果,不過從實現上來說,canvas的實現確實很是酷炫。
看似問題好像都要解決了,可是咱們彷佛忘了什麼?最開始的需求是要實現沿圓環的旋轉方向漸變。。可是以上的兩種方式都是純線性漸變和圓環自己是不要緊的。。有沒有辦法實現呢?答案固然是有的,先撇開canvas能夠本身訂製畫筆畫出的顏色不談,回到漸變上,咱們以前一直使用線性漸變,可是css中還有另外一種漸變——錐形漸變,可是它目前還在草案階段,實現的廠商也並很少(筆者僅用canary的實驗屬性中見過),那麼就不能實現了麼?答案固然也是否認的,想一想以前的clip,再想一想漸變,是否是有什麼神奇的事情就要發生呢!?
是否是以爲很神奇,讓咱們來看看具體如何實現的:
<style> .wheel, .colors, .color { content: ""; position: absolute; border-radius: 50%; width: 9.5em; height: 9.5em; } .wheel { display: block; z-index: 1; box-shadow: inset 0 16px 32px 14px rgba(0, 0, 0, 0.7); overflow: hidden; } .colors { list-style: none; position: relative; -webkit-filter: blur(10px); transform: rotate(170deg) scaleX(-1); } .color { clip: rect(0px, 9.5em, 9.5em, 4.75em); } .color:after { content: ""; position: absolute; border-radius: 50%; left: calc(50% - 4.75em); top: calc(50% - 4.75em); width: 9.5em; height: 9.5em; clip: rect(0px, 4.75em, 9.5em, 0px); } .color:nth-child(1):after { background-color: #9ED110; z-index: 12; transform: rotate(30deg); } .color:nth-child(2):after { background-color: #50B517; z-index: 11; transform: rotate(60deg); } .color:nth-child(3):after { background-color: #179067; z-index: 10; transform: rotate(90deg); } .color:nth-child(4):after { background-color: #476EAF; z-index: 9; transform: rotate(120deg); } .color:nth-child(5):after { background-color: #9f49ac; z-index: 8; transform: rotate(150deg); } .color:nth-child(6):after { background-color: #CC42A2; z-index: 7; transform: rotate(180deg); } .color:nth-child(7):after { background-color: #FF3BA7; z-index: 6; transform: rotate(180deg); } .color:nth-child(8):after { background-color: #FF5800; z-index: 5; transform: rotate(210deg); } .color:nth-child(9):after { background-color: #FF8100; z-index: 4; transform: rotate(240deg); } .color:nth-child(10):after { background-color: #FEAC00; z-index: 3; transform: rotate(270deg); } .color:nth-child(11):after { background-color: #FFCC00; z-index: 2; transform: rotate(300deg); } .color:nth-child(12):after { background-color: #EDE604; z-index: 1; transform: rotate(330deg); } .color:nth-child(n+7) { transform: rotate(180deg); } .left-wrapper,.right-wrapper{ position: absolute; left: 0; top: 0; width: 50%; height: 100%; overflow: hidden; } .left-circle,.right-circle{ position: absolute; top: 0; left: 0; width: 9.5em; height: 100%; border-radius: 50%; background: #ddd; } .left-circle{ clip: rect(0, 4.75em, auto, 0); } .right-circle{ clip: rect(0, auto, auto, 4.75em); left: -4.75em; } .right-wrapper{ left: 50%; } .wheel-mask{ position: absolute; top: 5%; left: 5%; width: 90%; height: 90%; border-radius: 50%; background-color: #fff; } </style> <div class="wheel"> <ul class="colors"> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> </ul> <div class='left-wrapper'> <div class="left-circle" style="transform:rotate(70deg)"></div> </div> <div class='right-wrapper'> <div class="right-circle" style="transform:rotate(180deg)"></div> </div> <div class="wheel-mask"></div> </div>
其實進度條的實現和以前並無什麼差異,最大的就是在於背景的看着像錐形漸變的一塊區域的實現,這實際上是csstrick上的一個有關實現錐形漸變的小技巧,原理是:
一、利用clip迭代出各類純色的三角形區域;
二、利用filter將純色區域模糊化,達到相似漸變的效果;
其實最開始就是上圖的效果,而後添加一個filter:blur(10px),模糊邊界,看起來就變成了這樣:
剩下的事情也就水到渠成了。
到最後,咱們來簡單總結一下用到的技術:
首先是CSS的相關遮擋原理,固然這個原理離不開一個。。一個已經在標準裏被廢棄的屬性——clip,滿屏的部分支持使人以爲倍感尷尬,不過咱們用到的clip的功能主要也就是裁剪,因此在短期內並不會有太大的影響。
那麼簡單的過一下api:
clip:rect(top, right, bottom, left);
如左圖所示,虛線的矩形即是實際裁剪的矩形的區域,右圖則是錐形色塊的實現原理:首先經過父容器將顯示區域裁成只有右邊一半,再在子元素裏用clip把左邊的元素也裁掉,在沒有旋轉的狀況下,就變成了什麼都看不到,而後經過rotate和li標籤間的遮擋關係來最終實現錐形的色塊。
最後的最後還有個小插曲,以上的代碼若是在.wheel中簡單的改動一下:
.wheel { display: block; z-index: 1; box-shadow: inset 0 16px 32px 14px rgba(0, 0, 0, 0.7); margin: 250px 0 0 250px; overflow: hidden; }
其實只是添加了一段margin,可是在某些android手機(實驗發現有問題的手機是s6 edge,系統版本6.0)上卻呈現了奇怪的效果(表現爲模糊的色塊不能徹底顯示或者直接不能顯示)。筆者開始以爲是合成層的問題,強制開啓了硬件加速以後確實好了,可是後來發現,去掉那一行margin也能恢復正常,可是加上就會致使整個着色區域在一個以屏幕左上角爲頂點有必定寬高的矩形範圍內,如圖:
多番查找以後仍沒有找到解決方案,(也許css filter在堪憂的性能確實使用得比較少也是緣由之一吧XD),暫時就先留在本文中,後續有了解決方案再更新吧。
2018.01.03補充:
最近看到了一個純CSS實現的一個餅圖,雖然不是否是徹底的環形的,可是感受也挺有意思的,研究下來發現了一個一直不太關注的知識點:
border-radius
相信不少同窗對這個屬性並不陌生,期初筆者也是這麼想的,直到遇到了要用一個矩形的容器畫一個半圓的時候:
腦中若是浮現的code是border-radius: 0 50% 50% 0的同窗,你獲得的將是下面
再讓咱們來看看border-radius的語法是怎樣的:
border-radius: <length-percentage>{1,4} [ / <length-percentage>{1,4} ]? where <length-percentage> = <length> | <percentage>
能夠看到,其實border-radius的完整參數應該是有兩組的,而咱們日常只寫了一組,前面那一組表明的是水平半徑,後面則表明垂直半徑,當後面的參數省略的時候,將使用前面一組的copy做爲缺省值,可能你們內心都不太清楚何謂水平和垂直半徑,其實這個border-radius的原理是畫一個半徑爲參數值的「橢圓」,當咱們做用在正方形的容器上時,垂直和水平半徑是同樣的,因此一般就忽略了,而當容器爲一個矩形的時候,咱們這須要考慮其每一個頂點的橢圓的半徑了,好比以下的三個例子:
左起第一個圖:正方形容器寬高均爲80px,當設置border-radius:50%時,等價於
border-radius: 40px 40px 40px 40px/40px 40px 40px 40px;
至關於畫了4個長軸長、短軸長也就是水平半徑和垂直半徑相同的四個「橢圓」;
第二個圖:在一個高80px寬40px的容器中,當設置border-radius: 50%時,等價於
border-radius: 20px 20px 20px 20px/40px 40px 40px 40px;
效果就至關於畫了4個長軸長(垂直半徑)爲40px,短軸長(水平半徑)爲20px的橢圓;
第三個圖:在一個高80px寬40px的容器中,當設置border-radius: 0 100% 100% 0/0 50% 50% 0時,等價於(注:此處的圖片有誤,實際上應該用下面的屬性畫出來的是隻有右邊的半圓)
border-radius: 0 40px 40px 0/0 40px 40px 0;
效果就至關於畫了2個水平垂直半徑相同的圓,如虛線的示意,因而就獲得了咱們想要的用矩形畫一個半圓的效果。
其中有關相對單位的轉換筆者就再也不贅述了,最後再放上那個餅圖的實際效果。