汕头网站制作:使用SVG设计灵活可维护的CSS饼图

2019.08.12 mf_web

95

饼图,即使是最简单的双色形式,传统上也只是简单的创建网络技术,尽管从简单的统计数据到进度指标和计时器的信息非常普遍。实现通常涉及使用外部图像编辑器为饼图的多个值创建多个图像,或者为更复杂的图表设计大型JavaScript框架。汕头网站制作

尽管这项壮举并不像以前那样难以实现,但它仍然没有简单的单线程。但是,今天有许多更好,更可维护的方法来实现它。

基于变换的解决方案

这个解决方案在标记方面是最好的:它只需要一个元素,其余部分使用伪元素,变换和CSS渐变。让我们从一个简单的元素开始:

<div class="pie"></div>

现在,让我们假设我们想要一个饼图,显示硬编码百分比** 20%**。我们将在以后努力使其灵活。让我们首先将元素设计为圆形,这将是我们的背景(图1):

图1:我们的起点(或者,饼图显示0%)

.pie {
  width: 100px; height: 100px;
  border-radius: 50%;
  background: yellowgreen;}

我们的饼图将是绿色(特别是“yellowgreen”),棕色(“#655”)显示百分比。我们可能会试图对百分比部分使用偏斜变换,但正如一些实验所示,它们被证明是一个非常混乱的解决方案。相反,我们将以**两种颜色**对我们圆圈的左右部分进行着色,并使用**旋转伪元素来仅发现我们需要的百分比**。

要为我们的棕色圆圈的右侧部分着色,我们将使用简单的线性渐变:

background-image:
  linear-gradient(to right, transparent 50%, #655 0);

图2:使用简单的线性渐变着色我们的圆棕色的右侧部分

正如您在图2中看到的,这就是所需要的。现在,我们可以继续设计将充当掩码的伪元素:

.pie::before {
  content: ’;
  display: block;
  margin-left: 50%;
  height: 100%;}

图3:这里用虚线表示将充当掩码的伪元素

您可以在图3中看到我们的伪元素当前位于相对于pie元素的位置。目前,它没有风格,也没有任何内容。它只是一个不可见的矩形。要开始设计样式,让我们做一些观察:

  • 因为我们希望它覆盖我们圈子的棕色部分,我们需要应用绿色背景,background-color: inherit以避免重复,因为我们希望它具有与其父级相同的背景颜色。

  • 我们希望它绕圆的中心,这是对伪元素的左侧的中间,所以我们应该应用transform-origin的0 50%它,或只是left。

  • 我们不希望它是一个矩形,因为它使得过去流血的饼图的边缘,所以我们需要既适用overflow: hidden于.pie,或适当border-radius,使之成为半圆。

总而言之,我们的伪元素的CSS将如下所示:

.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×360 = 72)的值,或者.2turn更可读。您可以在图5中看到它如何查找其他一些值。

图5:我们的简单饼图显示了不同的百分比; 从上到下:10%(36deg或.1turn),20%(72deg或.2turn),40%(144deg或.4turn)

我们可能会想到我们已经完成了,但遗憾的是,事情并非那么简单。我们的饼图非常适合显示0到50%的百分比,但如果我们尝试描绘60%的旋转(通过应用.6turn),图6就会发生。不过,不要失去希望,因为我们可以 - 而且我们会 - 解决这个问题!

图6:我们的饼图中断百分比大于50%(显示在这里:60%)

如果我们将50%-100%的百分比视为一个单独的问题,我们可能会注意到我们可以使用前一个解决方案的倒置版本:一个棕色的伪元素,分别从0to 旋转.5turn。因此,对于60%的饼,伪元素代码看起来像这样:

.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中看到这一点。因为我们现在已经找到了描述任何百分比的方法,我们甚至可以使用CSS动画将饼图从0%设置为100%,从而创建一个奇特的进度指示器:

@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;}

动画派

这一切都很好,但是我们如何设置具有不同百分比的多个静态饼图,这是最常见的用例?理想情况下,我们希望能够键入以下内容:

<div class="pie">20%</div><div class="pie">60%</div>

...并获得两个饼图,一个显示20%,另一个显示60%。首先,我们将探索如何使用内联样式来完成它,然后我们总是可以编写一个简短的脚本来解析文本内容并添加所谓的内联样式,以实现代码优雅,封装,可维护性,以及最重要的可访问性。

使用内联样式控制饼图百分比的挑战是,在伪元素上设置负责设置百分比的CSS代码。如您所知,我们无法在伪元素上设置内联样式,因此我们需要具有创造性。

注意:对于您希望使用频谱中的值而无需重复和复杂计算的其他情况,以及通过逐步调试动画,您可以使用相同的技术。查看一个更简单,孤立的技术示例。

解决方案来自最不可能的地方之一。我们将使用我们已经呈现的动画,但它将被暂停。我们不会像普通动画那样运行它,而是使用负动画延迟来静态地逐步进入动画中的任何一点并保持不变。困惑?是的,animation-delay不仅规范允许否定,但对于这样的情况非常有用:

负延迟有效。类似于' 0s' 的延迟,这意味着动画立即执行,但是会被延迟的绝对值自动推进,就像动画在过去的指定时间内开始一样,因此它似乎从中途开始有效期。

- CSS动画等级1

因为我们的动画暂停了,所以它的第一帧(由我们的负片定义animation-delay)将是唯一显示的帧。饼图上显示的百分比将是我们总持续时间的百分比animation-delay。例如,当前时间6s,我们需要一个animation-delay中-1.2s显示20%的比例。为了简化数学运算,我们将设置持续时间100s。请记住,因为动画永远暂停,我们指定的持续时间没有其他效果。

最后一个问题是:动画是在伪元素上,但我们想在.pie元素上设置内联样式。但是,因为没有动画<div>,我们可以将其设置animation-delay为内联样式,然后animation-delay: inherit;在伪元素上使用。将它们放在一起,我们对20%和60%饼图的标记将如下所示:

<div class="pie"
     style="animation-delay: -20s"></div><div class="pie"
     style="animation-delay: -60s"></div>

我们刚才为这个动画呈现的CSS代码现在变成了(不包括`.pie`规则,因为它保持不变):

@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;}

此时,我们可以将标记转换为使用百分比作为内容,就像我们最初的目标一样,并通过一个简单的脚本添加`animation-delay`内联样式:

$$('.pie').forEach(function(pie) {
  var p = parseFloat(pie.textContent);
  pie.style.animationDelay = '-' + p + 's';});

请注意,我们将文本保留原样,因为出于可访问性和可用性原因我们需要它。目前,我们的饼图如图8所示。我们需要隐藏文本,我们可以通过`color:transparent`来访问它,以便它保持**可选和可打印**。作为额外的润色,我们可以**在饼图**中居中百分比,以便当用户选择它时它不在随机位置。为此,我们需要:

图8:我们隐藏之前的文字

  • 将馅饼转换height为line-height(或添加line-height等于height,但这是无意义的代码重复,因为line-height将计算的高度也设置为)。

  • 通过绝对定位来确定伪元素的大小和位置,这样它就不会将文本向下推

  • 添加text-align: center;到文本的水平居中。

最终代码如下所示:

.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;}

### SVG解决方案

SVG使许多图形任务变得更容易,饼图也不例外。但是,我们将使用一个小技巧,而不是创建一个带有路径的饼图,这需要复杂的数学运算。

让我们从一个圆圈开始:

<svg width="100" height="100"><circle r="30" cx="50" cy="50" /></svg>

现在,让我们应用一些基本样式:

circle {
  fill: yellowgreen;
  stroke: #655;
  stroke-width: 30;}

注意:您可能知道,这些CSS属性也可以作为 SVG元素的属性使用,如果需要考虑可移植性,这可能很方便。

图9:我们的起点:一个带有#655中风的绿色SVG圆圈

您可以在图9中看到我们的描边圆圈.SVG描边不仅包含stroke和stroke-width属性。还有许多其他不太受欢迎的笔画相关属性可以微调笔画。其中之一是stroke-dasharray用于创建虚线笔划。例如,我们可以像这样使用它:

stroke-dasharray: 20 10;

图10:使用创建的简单虚线描边 stroke-dasharray

这意味着我们需要具有长度20间隙的短划线,10如图10中的那些。此时,您可能想知道这个SVG中风底漆与饼图有什么关系。当我们应用一个短划线宽度0且间隙宽度大于或等于我们圆周长的笔划时,它开始越来越清晰(C =2πr,所以在我们的例子中C =2π×30≈189):

stroke-dasharray: 0 189;

图11:多个stroke-dasharray值及其影响; 从左到右:0 189; 40 189; 95 189;150 189

正如您在图11中的第一个圆圈中所看到的,这完全消除了任何笔划,我们只剩下一个绿色圆圈。然而,当我们开始增加第一个值时,乐趣开始了(图11):因为间隙太长,我们不再获得虚线笔划,只是一个覆盖我们指定的圆周长百分比的笔划。

你可能已经开始弄清楚它的发展方向:如果我们将圆的半径减小到足以完全被它的笔划覆盖,我们最终会得到一个非常接近饼图的东西。例如,您可以在图12中看到当应用于半径为25a和a stroke-width的圆时的外观50,如下面的代码所生成的:

图12:我们的SVG图形开始类似于饼图

<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元素,我们可以设置:

svg {
  transform: rotate(-90deg);
  background: yellowgreen;
  border-radius: 50%;}

图13:最终的SVG饼图

你可以看到最后的结果如图13这一技术使得它更容易饼图从动画0%到100%。我们只需要创建一个CSS动画动画stroke-dasharray从0 158到158 158:

@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的饼图的标记现在将变为:

<svg viewBox="0 0 32 32">
  <circle r="16" cx="16" cy="16" /></svg>

CSS将成为:

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标记......

<div class="pie">20%</div><div class="pie">60%</div>

...并在每个`.pie`元素中添加一个内联SVG,包含所有必需的元素和属性。它还会添加一个` p>

$$('.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。或者,将其笔划长度添加到圆之前(下方)的笔划长度。您如何为使用第一个解决方案制作的饼图添加第三种颜色?

  • 我们不需要特别注意打印,因为SVG元素被认为是内容并且被打印,就像<img>元素一样。第一种解决方案取决于背景,因此不会打印。

  • 我们可以使用内联样式更改颜色,这意味着我们可以通过脚本轻松地更改它们(例如,取决于用户输入)。第一个解决方案依赖于伪元素,除了通过继承之外,它不能采用内联样式,这并不总是方便的。

circle {
  fill: yellowgreen;
  stroke: #655;
  stroke-width: 32;
  stroke-dasharray: 38 100; /* for 38% */}

注意**现在改变百分比**是多么容易。当然,即使有了这种简化,我们也不希望为每个饼图重复所有这些SVG标记。是时候让JavaScript帮助我们实现一点点自动化。我们将编写一个小脚本来采用如下的简单HTML标记......

<div class="pie">20%</div><div class="pie">60%</div>

...并在每个`.pie`元素中添加一个内联SVG,包含所有必需的元素和属性。它还会添加一个` p>

$$('.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。或者,将其笔划长度添加到圆之前(下方)的笔划长度。您如何为使用第一个解决方案制作的饼图添加第三种颜色?

  • 我们不需要特别注意打印,因为SVG元素被认为是内容并且被打印,就像<img>元素一样。第一种解决方案取决于背景,因此不会打印。

  • 我们可以使用内联样式更改颜色,这意味着我们可以通过脚本轻松地更改它们(例如,取决于用户输入)。第一个解决方案依赖于伪元素,除了通过继承之外,它不能采用内联样式,这并不总是方便的。

未来的饼图

圆锥形渐变在这里也会非常有用。饼图所需要的只是一个圆形元素,具有两个颜色停止的锥形渐变。例如,图5中的40%饼图将如下所示:

.pie {
  width: 100px; height: 100px;
  border-radius: 50%;
  background: conic-gradient(#655 40%, yellowgreen 0);}

此外,一旦[CSS Values Level 3](http://w3.org/TR/css3-values/#attr-notation)中定义的更新的`attr()`函数被广泛实现,您将能够控制具有简单HTML属性的百分比:

background: conic-gradient(#655 attr(data-value %), yellowgreen 0);

这也使得添加第三种颜色非常容易。例如,对于上面饼图上显示的饼图,我们只需添加两个色标:

background: conic-gradient(deeppink 20%, #fb3 0, #fb3 30%, yellowgreen 0);
这就是你用CSS设计一个简单的饼图的方法!

汕头网站制作

最新案例

寒枫总监

来电咨询

400-6065-301

微信咨询

寒枫总监

TOP