使用 CSS Grid 制作条形图

Avatar of Robin Rendle
Robin Rendle

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费信用额度!

编辑注意:这篇文章只是一个使用新 CSS 属性的实验,所以下面的代码不应在没有对可访问性进行重大改进的情况下使用。

我有一种奇怪的 图表强迫症,出于某种原因,我想找出用 CSS 制作图表的所有方法。 我想有两个原因。 首先,我认为用 CSS 在网络上设置图表和数据的方式有数百万种,这很有趣。 其次,这对学习新技术和不熟悉的技术非常有用。 在这种情况下:CSS Grid!

所以,我对图表这种痴迷让我思考:如何使用 CSS Grid 制作一个简单的响应式条形图,就像这样

让我们看看我是如何实现的!

快速简便的方法

由于 Grid 乍一看可能令人困惑和奇怪,因此让我们专注于首先创建一个非常简陋的原型。 为了开始,我们需要编写我们图表的标记

<div class="chart">
  <div class="bar-1"></div>
  <div class="bar-2"></div>
  <div class="bar-3"></div>
  <div class="bar-4"></div>
  <!--  all the way up to bar-12 -->
</div>

每个 bar- 类将构成我们图表中的一整条,而且,尽管这可能看起来很糟糕,但现在我们不会太担心语义或标记网格或数据。 这些稍后再说 - 让我们专注于 CSS,以便我们可以更多地了解 Grid。

好的,有了这些,我们现在可以进行样式设置了! 我们图表中需要 12 根条形图,它们之间有 5px 的间隙,所以我们可以使用相关的 CSS Grid 属性设置父类 .chart

.chart {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-column-gap: 5px;
}

如果您熟悉 Grid,这非常简单,但它实际上描述了以下内容:“我想要 12 列,每个子元素具有相等的宽度(1 分数),它们之间有 5px 的间隙”。

但是,现在,这里有一个巧妙的部分:使用 Grid,我们可以使用 grid-template-rows 属性来设置我们图表中每根条形图的高度

.chart {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: repeat(100, 1fr);
  grid-column-gap: 5px;
}

我们可以使用那个整洁的新属性在我们网格中创建 100 行,这样我们就可以将每根条形图设置为该高度的百分比,这将使计算变得简单。 同样,我们使用 repeat() 函数,以便我们每一行都具有相同的高度。

在我详细解释之前,让我们为图表指定一个最大宽度,并使用 flex 将其设置为屏幕中央

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background-color: #eee;
  display: flex;
  justify-content: center;
}

.chart {
  height: 100vh;
  width: 70vw;
  /* other chart styles go here */
}

此时,我们的图表仍然是空的,因为我们还没有告诉我们的子元素在网格中占用任何空间。 所以让我们修复它! 我们将选择包含 bar 的每个类,并使用 grid-row-startgrid-row-end 属性使它们填充网格中的垂直空间,因此最终我们将更改其中一个属性来定义每个条形图的自定义高度

[class*="bar"] {
  grid-row-start: 1;
  grid-row-end: 101;
  border-radius: 5px 5px 0 0;
  background-color: #ff4136;
}

所以,如果您对这些 grid-row 属性感到困惑,那就没问题! 我们告诉每个条形图从网格的最顶端(1)开始,然后在最底部(101)结束。 但是,当我们只告诉网格包含 100 行时,为什么我们要将 101 用作该属性的值? 在我们继续之前,让我们稍微探索一下!

网格线

在进行这个演示之前,我没有考虑过关于 Grid 的一个奇怪的事情,那就是 网格线 的概念,这对理解这个新的布局工具非常重要。 以下是四列四行网格中如何绘制网格线的图示

这个 新示例 包含四列和四行,具有以下样式

.grid {
  grid-gap: 5px;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(4, 1fr);
}

.special-col {
  grid-row: 2 / 4;
  background-color: #222;
}

grid-rowgrid-row-startgrid-row-end 的简写属性,第一个值是我们希望元素在网格中开始的位置,第二个值是我们希望它结束的位置。 但是! 这意味着我们希望此处的特殊元素从网格线 2 开始,并在网格线 4 结束 - 而不是在第 4 行的末尾。 如果我们希望那个黑色方块填充所有 4 行,那么它需要在第 5 行结束,或者 grid-row: 2 / 5,如果您仔细想想,这很有道理。

换句话说,我们不应该认为元素占用了网格的整行或整列,而应该认为它们只跨越了这些网格线。 在深入研究 Jen Simmons 最近关于此问题的教程 之后,我花了一段时间才在概念上理解并习惯这一点。

无论如何!

回到演示

所以这就是为什么在我们的图表演示中,我们将所有列都放在第 101 行而不是第 100 行的原因 - 因为我们希望它填充最后一行(100),所以我们必须将其发送到该特定网格线(101)。

现在,由于我们的 .chart 类使用 vw/vh 单位,我们也有一个很好的响应式图表,而无需进行太多操作。 如果您调整下面的图表大小,您会发现它会很好地收缩或拉伸,始终占用整个视窗

从这里,我们可以开始为每个单独的条形图设置样式,以赋予它们正确的数据,而且我们可以用很多不同的方法来做到这一点。 让我们看其中一种方法。

首先,让我们假设我们希望图表中的第一个条形图 .bar-1 占据图表高度的 50/100 或者一半。 我们可以编写以下 CSS 并完成它

[class*="bar"] {
  grid-row-end: 101;
}

.bar-1 {
  grid-row-start: 50;
}

这看起来不错! 但是,这里有一个问题 - 我们使用 grid-row-start 实际上声明的是让条形图从“50”开始,并在“101”结束,但这并不是我们真正想要的。 举个例子:假设在这个假设的例子中数据发生了变化,我们需要将它改为 20/100。 让我们回去更改该值

.bar-1 {
  grid-row-start: 20;
}

这是错误的! 我们希望条形图不要从网格中的 30 开始,而是占据图表高度的 30%。 我们可以将我们的值更改为 grid-row-start: 20;,或者我们可以改为使用 grid-row-end 属性,对吗? 好吧,不完全是

.bar-1 {
  grid-row-end: 20;
}

条形图的大小是正确的,但位置是错误的,因为我们告诉条形图在 30/100 处结束。 那么我们如何解决这个问题并使我们的代码超级易读呢? 好吧,一种方法是利用 Sass 为我们进行计算。 理想情况下,我们希望编写类似以下内容

.bar-1 {
  // makes a bar that's 60/100 and positioned at the bottom of our chart
  @include chartValue(60);
}

无论我们在该 mixin 中输入什么值,我们都希望始终获得图表在网格上的正确高度和位置。 为这个 mixin 提供动力的计算实际上非常简单:我们只需要取我们的值,从总行数中减去它,然后将其附加到 grid-row-start 属性,像这样

@mixin chartValue($data) {
  $totalRows: 101;
  $result: $totalRows - $data;
  grid-row-start: $result;
}

.bar-1 {
  @include chartValue(20);
}

所以,我们的 Sass mixin 生成的最终值为 grid-row-start: 81,但我们的代码非常清晰! 我们甚至不必查看网格就知道会发生什么 - 条形图将放置在网格的底部,并且该值将始终是正确的。

那么,我们如何创建所有这些网格类呢? 我认为一种不错的方法是让 Sass 为我们自动生成所有这些类。 只需对我们的代码进行一些修改,我们就可以执行类似的操作

$totalRows: 101;

@mixin chartValue($data) {
  $result: $totalRows - $data;
  grid-row-start: $result;
}

@for $i from 1 through $totalRows {
  .bar-#{$i} {
    @include chartValue($i);
  }
}

这将遍历我们图表中的所有行,并为该行大小生成一个单独的类。 因此,现在我们可以像这样更新我们的标记

<div class="bar-45"></div>
<div class="bar-100"></div>
<div class="bar-63"></div>
<div class="bar-11"></div>

就是这样! 我们不必手动为每个元素编写单独的类,而且我们可以通过简单地更改标记轻松地更新图表。 这个 Sass 循环将输出很多未使用的类,但有很多工具可以删除这些类。

我们可以在网格中做的最后一件事是通过奇偶数为每一列设置颜色

[class*="bar"]:nth-child(odd) {
  background-color: #ff4136; 
}

[class*="bar"]:nth-child(even) {
  background-color: #0074d9;
}

就是这样! 一个使用 CSS Grid 制作的响应式图表。 但是,我们可以做很多事情来整理这段代码。 我们可能应该做的第一件事是确保我们使用语义标记,并使用工具删除由我们的 Sass 循环输出的所有这些类。 我们还可以深入了解此图表在移动设备上的呈现方式,并考虑我们应该如何标记每一列和图表轴。

但就目前而言,这只是一个开始。 这篇文章的 TL;DR:CSS Grid 可用于各种事情,而不仅仅是将文本和图像并排放置。 它为我们打开了 Web 设计的一个全新分支,我们可以进行实验。