在涉及到CSS技術時,沒有人會比Lea Verou更執着、可是又足夠聰明,努力去找尋問題的各類解決方案。最近,Lea本身撰寫、設計和出版了一本書——CSS Secrets,這本書很是有趣,包括一些CSS小技巧以及解決常見問題的技術。若是你以爲本身的CSS技術還不錯,看看這本書,你會吃驚的。在這篇文章中,咱們發佈了書裏的一些片斷,這也被髮表在Lea最近在SmashingConf New York的演講內容中——用CSS設計簡單的餅圖。注意,由於瀏覽器的支持有限,有些demo可能不能正常運行。——編輯html
餅圖,即便是最簡單的只有兩種顏色的形式,用Web技術建立也並不簡單,儘管都是一些常見的信息內容,從簡單的統計到進度條指標還有計時器。一般是使用外部圖像編輯器來分別爲多個值建立多個圖像來實現,或是使用大型的JavaScript框架來設計更復雜的圖表。css3
儘管這個東西並不像它曾經看起來那麼難以實現,可是也沒有什麼直接而且簡單的方法。可是,如今已經有不少更好、更易於維護的方式來實現它。git
這個方案從HTML的角度來講是最好的:它只須要一個元素,其它的均可以用僞元素、變換和CSS漸變完成。咱們從下面這個簡單的元素開始:程序員
1
|
<div class="pie"></div>
|
如今,假設咱們但願顯示一個 20% 比例的餅圖。靈活性的問題咱們後面再解決。咱們先給元素添加樣式,讓它變成一個圓,也就是咱們的背景:github
圖1:第一步是先畫一個圓(或者能夠說是顯示0%比例的餅圖)web
1
2
3
4
5
|
.pie {
width: 100px; height: 100px;
border-radius: 50%;
background: yellowgreen;
}
|
咱們的餅圖是綠色(特指 yellowgreen )和棕色( #655 )顯示的百分比。可能會在比例部分嘗試使用 transform 中的 skew ,可是通過幾回試驗以後代表,這是一個很是混亂的方案。所以,咱們用這兩種顏色爲這個餅圖的左右部分分別着色,而後對於咱們想要的百分比,使用旋轉的僞元素來實現。vim
咱們使用一個簡單的線性漸變,給右半部分着棕色:瀏覽器
1
|
background-image: linear-gradient(to right, transparent 50%, #655 0);
|
圖2:用一個簡單的線性漸變給右半圓着棕色app
如圖2所示,這樣就完成了。如今,咱們能夠繼續爲僞元素添加樣式,讓它成爲一個蒙版:
1
2
3
4
5
6
|
.pie::before {
content: '';
display: block;
margin-left: 50%;
height: 100%;
}
|
圖3:虛線內的內容表示僞元素將做爲蒙版的區域
你能夠在圖3中看到咱們的僞元素當前定位相對於咱們的pie元素。目前,它尚未添加樣式,也沒有覆蓋任何東西,只是一個透明的矩形。在開始添加樣式以前,咱們先來分析一下:
綜上所述,僞元素的CSS樣式以下:
1
2
3
4
5
6
7
8
9
|
.pie::before {
content: '';
display: block;
margin-left: 50%;
height: 100%;
border-radius: 0 100% 100% 0 / 50%;
background-color: inherit;
transform-origin: left;
}
|
圖4:添加樣式以後的僞元素(這裏用虛線表示)
注意:不要使用 background: inherit; ,要用 background-color: inherit
;
,不然父元素背景圖像上的漸變也會被繼承
咱們的餅圖目前如圖4所示。如今開始有趣起來了!咱們能夠開始旋轉僞元素,給它應用一個rotate() 變換。要顯示 20% 的比例,咱們能夠給它一個 72deg ( 0.2 x 360 = 72 ),或 .2turn ,這個可讀性更好。你能夠在圖5中看到不一樣旋轉角度值的結果。
圖5:分別展現不一樣百分比的餅圖,從左到右: 10% ( 36deg 或 .1turn ), 20% ( 72deg 或 .2turn ), 40% ( 144deg 或 .4turn )
你可能會想咱們已經完成了,可是它可沒這麼簡單。咱們的餅圖在展現0
到50%
的大小的內容時是沒有任何問題的,可是若是咱們要描繪一個 60% 的旋轉(經過應用 .6turn ),就會發生如圖6的狀況。可是,別擔憂,咱們能夠解決這個事情!
圖6:對於超過50%的比例,咱們的餅圖就跪了orz(這裏的是60%)
若是咱們把 50%-100% 比例的狀況做爲單獨的一個問題,可能會注意到可使用以前的解決方案的反相版本:從0
到.5turn
旋轉的棕色僞元素。因此,對於一個60%
的餅圖,僞元素的CSS代碼以下:
1
2
3
4
5
6
7
8
9
10
|
.pie::before {
content: '';
display: block;
margin-left: 50%;
height: 100%;
border-radius: 0 100% 100% 0 / 50%;
background: #655;
transform-origin: left;
transform: rotate(.1turn);
}
|
圖7: 60% 餅圖的正確打開方式~
你能夠在圖7中看到結果。由於咱們已經制定了一個能夠描繪出任何百分比的方法,咱們甚至能夠爲餅圖從0%
到100%
添加動畫效果,建立出一個有趣的進度條:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@keyframes spin {
to { transform: rotate(.5turn); }
}
@keyframes bg {
50% { background: #655; }
}
.pie::before {
content: '';
display: block;
margin-left: 50%;
height: 100%;
border-radius: 0 100% 100% 0 / 50%;
background-color: inherit;
transform-origin: left;
animation: spin 3s linear infinite,
bg 6s step-end infinite;
}
|
See the Pen zGbNLJ by Airen (@airen) on CodePen.
顯示沒有問題,可是咱們若是給多個不一樣百分比的靜態餅圖添加樣式呢,最多見的用例是?在理想狀況下,咱們但願能夠簡單地輸入以下的內容:
1
2
|
<div class="pie">20%</div>
<div class="pie">60%</div>
|
而後就能夠獲得兩個餅圖,一個表示20%
,一個表示60%
。首先,咱們先研究一下如何使用內聯樣式來完成,而後咱們能夠寫一個簡短的腳原本解析文本內容,對應地添加內聯樣式,並且要代碼優雅、封裝、可維護性,還有最重要的一點,可訪問性。
使用內聯樣式控制餅圖百分比的一個困難是:用於設置百分比CSS代碼是用僞元素完成的。並且你也知道,咱們不能給僞元素設置內聯樣式,因此咱們須要創新。
注意:若是你想要使用的值是在某個不須要通過重複的複雜的計算的範圍內的狀況,你可使用相同的技術,包括經過它們一步一步調試動畫的狀況。看該技術的一個簡單的示例。
See the Pen YXgNOK by Airen (@airen) on CodePen.
解決方案來自最不可能的地方之一。咱們將要使用咱們已經介紹過的動畫,可是它是暫停狀態的。咱們不會讓它像一個正常的動畫那樣運行,咱們將使用負延遲來讓它能夠靜態地暫停在某個點。很奇怪?一個負的animation-delay
的值不只在規範中是容許的,在相似這樣的案例中也是很是好用:
負延遲是有效的。和
0s
的延遲相似,它表示動畫將當即執行,可是是根據延遲的絕對值來自動運行的,因此若是動畫已經在指定的時間以前就開始運行了,那它就會直接從active的時間中途運行。 —CSS Animations Level 1
由於咱們的動畫是暫停的,它的第一幀就是咱們惟一展現的那一幀(經過咱們的animation-delay
定義)。餅圖上顯示的百分比將會是咱們的animation-delay
的總時間。例如,當前的持續時間是6s
,咱們的 animation-delay 值爲-1.2s
則顯示20%
的百分比。爲了簡化計算,咱們設置一個100s
的持續時間。記住由於咱們的動畫是永遠暫停的,咱們給它指定的延遲大小並不會有什麼影響。
還有最後一個問題:動畫是賦給僞元素的,可是咱們想要給.pie
元素設置內聯樣式。由於<div>
上沒有動畫,咱們能夠給它設置animation-delay
做爲內聯樣式,而後給僞元素應用 animation-delay: inherit; 。綜上所述,20%
和60%
的餅圖的HTML代碼以下:
1
2
|
<div class="pie" style="animation-delay: -20s"></div>
<div class="pie" style="animation-delay: -60s"></div>
|
剛剛提出的這個動畫的CSS代碼以下(省略 .pie 規則,由於沒有改變):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@keyframes spin {
to { transform: rotate(.5turn); }
}
@keyframes bg {
50% { background: #655; }
}
.pie::before {
/* [Rest of styling stays the same] */
animation: spin 50s linear infinite, bg 100s step-end infinite;
animation-play-state: paused;
animation-delay: inherit;
}
|
這時候,能夠把HTML標籤改爲使用百分比做爲內容,和一開始但願的同樣,而後經過一個簡單的腳本爲其添加 animation-delay 內聯樣式。
1
2
3
4
|
$$('.pie').forEach(function(pie) {
var p = parseFloat(pie.textContent);
pie.style.animationDelay = '-' + p + 's';
});
|
圖8:沒有隱藏文本前的圖
height
轉換成 line-height (或添加一個和height
值相等的line-height
,可是這值是毫無心義的重複代碼,由於line-height
會自動計算height
的值)。最後的代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
.pie {
position: relative;
width: 100px;
line-height: 100px;
border-radius: 50%;
background: yellowgreen;
background-image: linear-gradient(to right, transparent 50%, #655 0);
color: transparent;
text-align: center;
}
@keyframes spin {
to { transform: rotate(.5turn); }
}
@keyframes bg {
50% { background: #655; }
}
.pie::before {
content: '';
position: absolute;
top: 0; left: 50%;
width: 50%; height: 100%;
border-radius: 0 100% 100% 0 / 50%;
background-color: inherit;
transform-origin: left;
animation: spin 50s linear infinite, bg 100s step-end infinite;
animation-play-state: paused;
animation-delay: inherit;
}
|
See the Pen qdvRMv by Airen (@airen) on CodePen.
SVG使得不少圖形工做變得更加簡單,餅圖也不例外。可是,用path
路徑建立餅圖,須要複雜的數學計算,咱們可使用一點小技巧來代替。
咱們從一個圓開始:
1
2
3
|
<svg width="100" height="100">
<circle r="30" cx="50" cy="50" />
</svg>
|
如今,給它應用一些基礎的樣式:
1
2
3
4
5
|
circle {
fill: yellowgreen;
stroke: #655;
stroke-width: 30;
}
|
注意:你可能知道,這些CSS屬性也能夠做爲SVG元素的屬性使用,若是把可移植性考慮在內的話這可能挺方便的。
圖9:從一個綠色的SVG圓形,帶一個胖胖的#655
描邊開始
你能夠在圖9中看到咱們繪製的加了描邊的圓。SVG描邊不止有stroke
和stroke-width
屬性。還有不少不是特別流行的描邊相關的屬性能夠用於對描邊進行微調。其中一個是stroke-dasharray
,用於建立虛線描邊。例如,咱們可使用以下:
1
|
stroke-dasharray: 20 10;
|
圖10:一個簡單的虛線描邊,經過stroke-dasharray
屬性建立
這行代碼的意思是咱們的虛線是20
的長度加上10
的邊距,如圖10所示。在這裏,你可能會好奇這個SVG描邊屬性和餅圖究竟有什麼關係呢。若是咱們給描邊應用一個值爲0
的虛線寬度,和一個大於或等於咱們當前圓的周長的邊距,它可能就清晰一些了(計算周長: C = 2πr , 因此在這裏 C = 2π × 30 ≈ 189 ):
1
|
stroke-dasharray: 0 189;
|
圖11:不一樣stroke-dasharray
值對應的效果;從左到右: 0 189; 40 189; 95 189; 150 189
如圖11中的第一個圓所示,它的描邊的都被移除了,只剩下一個綠色的圓。可是,當咱們開始增大第一個值的時候,有趣的事情發生了(圖11):由於邊距太長,咱們就沒有虛線描邊了,只有一個描邊覆蓋了咱們指定的圓的周長的百分比。
你可能已經開始弄清楚了這是怎麼回事:若是咱們把圓的半徑減少到必定程度,它可能就會徹底被它的描邊覆蓋,最後獲得的是一個很是相似於餅圖的東西。例如,你能夠在圖12中看到:當給圓應用一個25
的半徑和一個50
的stroke-width
,像下面的效果:
圖12:咱們的SVG圖像開始像一個餅圖了O(∩_∩)O
記住:SVG描邊老是相對於元素邊緣一半在內一半在外的(居中的)。未來應該能夠控制這一行爲。
1
2
3
4
5
6
7
8
9
10
|
<svg width="100" height="100">
<circle r="25" cx="50" cy="50" />
</svg>
circle {
fill: yellowgreen;
stroke: #655;
stroke-width: 50;
stroke-dasharray: 60 158; /* 2π × 25 ≈ 158 */
}
|
如今,把它變成咱們在上一個解決方案中製做的餅圖的樣子是很是容易的:咱們只須要在描邊下面添加一個更大的綠色圓形,而後逆時針旋轉90°
,這樣它的起點就在頂部中間。由於<svg>
元素也是HTML元素,咱們能夠給它添加樣式:
1
2
3
4
5
|
svg {
transform: rotate(-90deg);
background: yellowgreen;
border-radius: 50%;
}
|
圖13:最後的SVG餅圖
你能夠在圖13中看到最終結果。這種技術可讓餅圖更容易實現從0%
到100%
變化的動畫。咱們只須要建立一個CSS動畫,讓stroke-dasharray
從 0 158 變成 158 158 :
1
2
3
4
5
6
7
8
9
10
11
|
@keyframes fillup {
to { stroke-dasharray: 158 158; }
}
circle {
fill: yellowgreen;
stroke: #655;
stroke-width: 50;
stroke-dasharray: 0 158;
animation: fillup 5s linear infinite;
}
|
做爲一個額外的改進,咱們能夠在圓上指定一個特定半徑,使其周長無限接近100
,這樣咱們能夠用百分比指定stroke-dasharray
的長度,而不須要作計算。由於周長是2πr
,咱們的半徑則是100 ÷ 2π ≈ 15.915494309
,約等於16
。咱們還能夠用viewBox
特性指定SVG的尺寸,可讓它自動調整爲容器的大小,不要使用width
和height
屬性。
通過以上調整,圖13的餅圖的HTML標籤以下:
1
2
3
|
<svg viewBox="0 0 32 32">
<circle r="16" cx="16" cy="16" />
</svg>
|
CSS以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
svg {
width: 100px; height: 100px;
transform: rotate(-90deg);
background: yellowgreen;
border-radius: 50%;
}
circle {
fill: yellowgreen;
stroke: #655;
stroke-width: 32;
stroke-dasharray: 38 100; /* for 38% */
}
|
注意如今百分比已經能夠很方便地改變了。固然,即便已經簡化了標籤,咱們仍是不想在繪製每一個餅圖的時候都重複一遍全部這些SVG標籤。這是時候拿出JavaScript來幫咱們一把了。咱們寫一個簡單的腳本,讓咱們的HTML標籤直接簡單地這樣寫:
1
2
|
<div class="pie">20%</div>
<div class="pie">60%</div>
|
而後在每一個.pie
元素裏邊添加一個內聯SVG,包括全部須要的元素和屬性。它還會添加一個<title>
元素,爲了增長可訪問性,這樣屏幕閱讀器用戶還能夠知道當前的餅圖表示的百分比。最後的腳本以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
$$('.pie').forEach(function(pie) {
var p = parseFloat(pie.textContent);
var NS = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(NS, "svg");
var circle = document.createElementNS(NS, "circle");
var title = document.createElementNS(NS, "title");
circle.setAttribute("r", 16);
circle.setAttribute("cx", 16);
circle.setAttribute("cy", 16);
circle.setAttribute("stroke-dasharray", p + " 100");
svg.setAttribute("viewBox", "0 0 32 32");
title.textContent = pie.textContent;
pie.textContent = '';
svg.appendChild(title);
svg.appendChild(circle);
pie.appendChild(svg);
});
|
就是它了!你可能會以爲CSS方法比較好,由於它的代碼比較簡單並且更靠譜。可是,SVG方法相比純CSS方案仍是有必定的優點的:
stroke-dashoffset
設置它的描邊屬性。而後,將它的描邊長度添加到下方的圓的描邊長度上。若是是前面那個CSS的方案,你要如何給餅圖添加第三種顏色呢?<img>
元素同樣,被默認爲是內容的一部分,打印徹底沒有問題。第一種方案取決於背景,因此不會被打印。都說程序員的工資高,卻不多瞭解他們加班的痛苦,你是否是每次也在內心想,按時間折算下來這個工資都給少了,因而會想在內心吶喊,要麼漲工資,要麼漲工資,要麼漲工資,爲何??由於不讓咱們加班,這是不可能的!!!
想要顛覆本身的工做模式嗎?想要減小本身的加班時間嗎?加入咱們,和咱們一塊兒探尋屬於咱們程序員的自由模式吧!
一款針對程序員的原生APP,以共享知識技能爲目的,以懸賞方式在線互動交互平臺。
咱們擁有高達近20人頂尖的技術團隊,以及優秀的產品及運營團隊。團隊領軍人物均在行業內有10年以上的豐富經驗。
如今咱們正在招募原始的參與英雄,您將同咱們一塊兒改變程序員的工做方式,改變程序員的世界!同時也會有豐厚的報酬。做爲咱們的原始的參與者,您將同咱們一塊兒體驗這款程序員神器,您能夠提出專業的建議,咱們會虛心採納。每個人都會是英雄,而您就會是咱們須要的英雄!同時您也能夠邀請您的朋友一塊兒參與這場英雄的招募互動。
咱們不會耽誤你太多時間,咱們只須要您的專業見解,只要您從一個月內抽出1個小時,之後您天天均可以節省兩個小時,一切都是爲了咱們本身!
來?仍是不來?
接頭人暗號:1955246408 (QQ)