更多 CSS 图表,使用网格和自定义属性

Avatar of Miriam Suzanne
Miriam Suzanne

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

我喜欢 Robin 最近的帖子,他尝试使用 CSS Grid 来制作柱状图。 事实上,我一直在我客户的一个项目中使用类似的方法,使用 CSS Grid 构建一个日历计划器。 这是一个不同的用例,但基本技巧相同:使用网格布局来可视化数据。

(我建议先阅读 Robin 的文章,因为我在他的图表基础上进行构建。)

Robin 的方法依赖于一个大型 Sass 循环来生成 100 个潜在的类名,即使最终的图表中只使用了不到 12 个。 在生产中,我们希望使用更直接、更高效的方法,具有更好的语义,因此我转向了定义列表和 CSS 变量(又名自定义属性)来构建我的图表。

这是最终的结果

查看代码笔 CSS 网格 + 变量中的柱状图,作者是 Miriam Suzanne (@mirisuzanne),发布在 CodePen 上。

让我们深入了解它!

首先是标记

Robin 提出的是一个概念性实验,所以他省略了许多现实世界中的数据和可访问性问题。 由于我的目标是(虚构的)生产代码,我想确保它具有语义性和可访问性。 我从 Robin 的图表中的评论中借鉴了年份轴的想法,并将所有内容都移到了定义列表中。 每个年份都与列表中相应的百分比相关联

<dl class="chart">
  <dt class="date">2000</dt>
  <dd class="bar">45%</dd>

  <dt class="date">2001</dt> 
  <dd class="bar">100%</dd>
  
  <!-- etc… -->
</dl>

可能还有其他方法可以以可访问的方式进行标记,但我认为 dl 很干净直观 - 所有的数据和关联对都作为结构化文本提供。 默认情况下,这将以可读的格式显示年份/百分比对。 现在我们必须让它变得漂亮。

网格设置

我从 Robin 的网格开始,但我的标记需要为 .date 元素添加一个额外的行。 我将它添加到 grid-template-rows 的末尾,并将我的日期/条形元素放置在适当的位置

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

.date {
  /* fill the bottom row */
  grid-row-start: -2;
}

.bar {
  /* end before the bottom row */
  grid-row-end: -2;
}

通常,我会使用 auto 来表示最后一行,但我需要一个明确的高度才能使背景网格正常工作。 可能不值得,但我很享受它。

将数据传递到 CSS

此时,CSS 无法访问与图表样式相关的数字。 我们没有钩子来将各个条形设置为不同的高度。 Robin 的解决方案是为每个条形值使用单独的类名,并使用 Sass 循环为每个值创建自定义类。 这有效,但我们最终会得到一个很长的类列表,而这些类可能永远不会被使用。 是否有更直接的方法将数据传递到 CSS 中?

最直接的方法可能是内联样式

<dd class="bar" style="grid-row-start: 56">45%</dd>

起始位置是网格线的总数(比行数多一个,在本例中为 101),减去给定条形的总值:101 - 45 = 56。 这效果很好,但现在我们的标记和 CSS 紧密耦合。 使用 CSS 变量,我们可以传入原始数据,让 CSS 决定如何使用它

<dd class="bar" style="--start: 56">45%</dd>

在 CSS 中,我们可以将其连接到 grid-row-start

.bar {
  grid-row-start: var(--start);
}

我们用一行动态 CSS 替换了类名循环和膨胀的 100 类输出。 变量还消除了通常与 HTML 中的 CSS 相关的危险。 虽然像 grid-row-start 这样的内联属性几乎不可能从 CSS 文件中覆盖,但内联变量可以被 CSS 忽略。 我们不必担心任何特殊性/级联问题。

数据驱动的背景

作为奖励,我们可以使用数据做更多的事情,而不仅仅是提供网格位置 - 重用它来设置备用选项的样式,甚至根据相同的数据调整条形颜色

.bar {
  background-image: linear-gradient(to right, green, yellow, orange, red);
  background-size: 1600% 100%;
  
  /* turn the start value into a percentage for position on the gradient */
  background-position: calc(var(--start) * 1%) 0;
}

我从绿色到黄色、橙色,然后到红色的水平背景渐变开始。 然后,我使用 background-size 使渐变比条形宽得多 - 每个颜色至少 200%(800%)。 更大的渐变宽度会使渐变不太明显,所以我选择了 1600% 来保持它微妙。 最后,使用 calc() 将我们的起始位置 (1-100) 转换为百分比,我可以根据值调整背景位置的左或右 - 根据百分比显示不同的颜色。

背景网格也是使用变量和背景渐变生成的。 可悲的是,次像素舍入使其有点不可靠,但您可以玩弄 --line-every 值来更改细节级别。 四处看看,看看你能做出什么其他改进!

添加比例 [不使用 Firefox]

现在,我们传入的是起始位置,而不是纯值(“45%”对应的“56”)。 该起始位置基于总体比例为 100% 的假设。 为了使它成为更灵活的工具,我认为将所有数学运算(包括比例)都包含在 CSS 中会很有趣。 以下是它的样子

<dl class="chart" style="--scale: 100">
  <dt class="date">2000</dt>
  <dd class="bar" style="--value: 45">45%</dd>

  <dt class="date">2001</dt> 
  <dd class="bar" style="--value: 100">100%</dd>
  
  <!-- etc… -->
</dl>

然后,我们可以在应用它之前在 CSS 中计算 --start 值。

.bar {
  --start: calc(var(--scale) + 1 - var(--value));
  grid-row-start: var(--start);
}

有了 CSS 中的总体比例和单个值,我们就可以分别操作两者。 将比例更改为 200%,然后观察图表相应更新

查看代码笔 带有比例的柱状图 - 不使用 Firefox,作者是 Miriam Suzanne (@mirisuzanne),发布在 CodePen 上。

Chrome 和 Safari 都很好地处理了它,但 Firefox 对网格定位中的 calc 值似乎不太满意。 我想他们最终会解决它。 目前,我们只能将一些计算排除在 CSS 之外。

令人伤心,但我们会习惯它。 😉

我们可以做更多的事情,为旧版浏览器提供备用选项 - 但我认为这确实是一个可行的选择,有可能实现可访问性、语义性、高效性和美观性。 谢谢你开启了这场讨论,Robin!