在涉及到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)