Newshot
Account

使用现代 CSS 创建图表与图形

Translation(model translation, not the source's own text)

文章标题:Creating Diagrams and Graphs Using Modern CSS

正文:

使用现代 CSS 创建图表和图形

在两篇详细的文章(希望您已阅读!)之后,我们拥有了一种绝佳的技术,可以使用 border-shape 和 shape() 来用线条连接两个元素,并且可以塑造线条形状。

在本文中,我们将探索更多常用于创建图表和图形的连接类型。当然,我们无法涵盖所有类型,但这项技术是通用的,因此如果您理解透彻,就可以轻松调整代码来创建自己的连接。

  • 两个圆、一个箭头和锚点定位
  • 两个圆、一个连接和 border-shape
  • 使用现代 CSS 创建图表和图形

在开始之前,请允许我提醒您,目标并不是发明一种新的或更好的使用 CSS 创建图表的方法。这仍然是一个实验,旨在探索现代特性并创造仅使用 CSS 无法实现(或非常困难)的东西。这也可以作为概念验证,证明使用 CSS 确实是可行的。如果可行,它可以带来更好、更直观的实现。

闲话少说,让我们开始吧!

在撰写本文时,只有 Chrome 和 Edge 完全支持我们将要使用的特性。

动态连接

第一种连接类型根据元素位置和形状进行调整。它类似于我们在之前文章中做的,但这次我们不仅连接中心点,还考虑侧边。

让我们从一个演示开始:

如果您拖动元素,您会注意到连接会考虑最接近的侧边来连接两个元素,如果它们靠得太近,则会连接它们的中心。一个动态连接!

如果您仔细观察,您会注意到我们有四种不同的位置,因此有四种不同的形状(一条曲线和三条直线)。

四对蓝绿色和橙色圆角方块,每对由一条线连接:一条从一侧到另一侧的S形曲线,以及三条根据方块位置连接最近侧边或中心的直线。
四对蓝绿色和橙色圆角方块,每对由一条线连接:一条从一侧到另一侧的S形曲线,以及三条根据方块位置连接最近侧边或中心的直线。

您忘记了一些情况吗?还有更多位置!

是的,但别忘了锚点位置配置。我们有四个区域,一次只显示一个,它们都是对称的,所以缩放变换给出了其余的部分。如果这最后一句话听起来奇怪,请回到第一篇文章复习一下。

形状与我们上一篇文章中使用的相同,border-shape 代码也相同。


        .link *:before {
  border-shape: if(
    style(condition_1):
      shape(from ?? ??,curve to 50% 50% with ?? ??,smooth to ?? ??);
    style(condition_2):
      shape(from ?? ??,curve to 50% 50% with ?? ??,smooth to ?? ??);
    style(condition_3):
      shape(from ?? ??,curve to 50% 50% with ?? ??,smooth to ?? ??);
    else:
      shape(from ?? ??,curve to 50% 50% with ?? ??,smooth to ?? ??))
    padding-box;
}代码语言: CSS (css)
      

到目前为止,与上一篇文章相比没有什么变化。我们从两种形状变成了四种,所以我们需要三个条件而不是一个。

让我们从第一个条件开始:当两个元素靠近时,从第一个元素的中心到第二个元素的中心画一条线。换句话说,当它们的距离小于指定值时,我们从伪元素的左上角到右下角画一条线。


        style((hypot(100cqw,100cqh) < 200px)):
      shape(from 0 0,curve to 50% 50% with 50% 50%,smooth to 100% 100%);代码语言: CSS (css)
      

shape() 的值应该清楚,如果您想知道 hypot() 是什么,它是斜边数学函数。它计算伪元素的对角线,即两个圆心之间的距离。

我们来看第二个条件:当元素之间的垂直距离小于指定值时,我们连接右边缘和左边缘。


        style(100cqh < 200px):
      shape(from 60px 0,curve to 50% 50% with 50% 50%,smooth to calc(100% - 60px) 100%);Code language: CSS (css)
      

看到了吗?与之前的形状相比,我只是调整了两个值。那个 60px 是元素尺寸的一半。它在演示中定义为变量,是的,这对这类连接来说是个小缺点,因为需要知道元素尺寸。

我想你明白思路了,所以这里是完整代码:


        .link *:before {
  border-shape: if(
    style((hypot(100cqw,100cqh) < 200px)):
      shape(from 0 0,curve to 50% 50% with 50% 50%,smooth to 100% 100%);
    style((100cqh < 200px) and (100cqh < 100cqw)):
      shape(from 60px 0,curve to 50% 50% with 50% 50%,smooth to calc(100% - 60px) 100%);
    style((100cqw < 250px) and (100cqh > 100cqw)):
      shape(from 0 60px,curve to 50% 50% with 50% 50%,smooth to 100% calc(100% - 60px));
    else:
      shape(from 60px 0,curve to 50% 50% with 200px 0,smooth to calc(100% - 60px) 100%))
    padding-box;
}Code language: CSS (css)
      

我为第二和第三个条件添加了一条额外规则,以说明我们可以使用 and/or 来创建精确条件。在这种情况下,我想避免一些在我看来连接效果不好的边界情况。

一个青色方块位于橙色方块上方并稍偏左,两者最近的边缘之间由一条短的、近乎垂直的直线连接。
一个青色方块位于橙色方块上方并稍偏左,两者最近的边缘之间由一条短的、近乎垂直的直线连接。

对于两端,逻辑完全相同。我们保留相同的 if() 结构,并更新 shape() 的值。因此,我在演示中写了以下代码:


        .link *:before,
.link *:after {
  border-shape: if(
    style((hypot(100cqw,100cqh) < 200px)):
      var(--shape1);
    style((100cqh < 200px) and (100cqh < 100cqw)):
      var(--shape2);
    style((100cqw < 250px) and (100cqh > 100cqw)):
      var(--shape3);
    else:
      var(--shape4))
    padding-box;
}Code language: CSS (css)
      

然后,每个伪元素定义自己的形状。

如果你想知道指定值、条件、它们的数字等背后的逻辑,答案是:没有逻辑。这属于个人选择。正如我在引言中所说,这是一种通用技术。我只是基于上一篇文章中学到的内容,创建了一个看起来更复杂的连接。

试一试!拿起笔和纸,画出连接,然后尝试编码实现。从一个形状开始,然后考虑不同的条件以及你想如何更新它。别害羞;这是个有趣的练习。在进入下一节之前做,因为我们要稍微提高一点难度。

别忘了,我们可以添加任意多的元素来创建精美的图!

![一张由六个彩色节点组成的图,节点标记为 A

以下是更好的一张图,展示了四个不同区域(在第一篇文章中已解释)以及每个区域中的形状:

四张标记为 A、B、C、D 的图,每张图都在一个细长矩形的对角处各有一个青绿色方块和一个橙色方块,两者由一条从右向左的曲线连接,曲线的形状在左右两列之间有所不同。
四张标记为 A、B、C、D 的图,每张图都在一个细长矩形的对角处各有一个青绿色方块和一个橙色方块,两者由一条从右向左的曲线连接,曲线的形状在左右两列之间有所不同。

A 和 C 仍然对称(B 和 D 也是如此),但 A 和 B 不再对称(C 和 D 同理)。这意味着我们需要一种不同的策略,而不是简单地处理一个位置再翻转得到其他位置。

为此,我将更新锚点位置配置。换一个略有不同、更适合本案例的配置。

我们更新以下内容:


        .link > * {
  position: absolute;
  --_x: calc(anchor(--c1 inside) + anchor-size(--c1)/2);
  --_y: calc(anchor(--c2 inside) + anchor-size(--c2)/2);
}

.link :is(a,b) {top:  var(--_x); bottom: var(--_y);}
.link :is(a,c) {left: var(--_x); right:  var(--_y);}
.link :is(c,d) {top:  var(--_y); bottom: var(--_x);}
.link :is(b,d) {left: var(--_y); right:  var(--_x);}

.link b {scale: -1  1}
.link c {scale:  1 -1}
.link d {scale: -1 -1} Code language: CSS (css)
      

将其替换为:


        .link > * {
  position: absolute;
  --_x: calc(anchor(var(--x) inside) + anchor-size(var(--x))/2);
  --_y: calc(anchor(var(--y) inside) + anchor-size(var(--y))/2);
  container-type: size;
}
.link :is(a,b) {top:  var(--_x); bottom: var(--_y)}
.link :is(a,c) {left: anchor(var(--x) outside); right: anchor(var(--y) outside)}
.link :is(c,d) {top:  var(--_y); bottom: var(--_x)}
.link :is(b,d) {left: anchor(var(--y) inside); right: anchor(var(--x) inside)}

.link :is(c,d) {scale:  1 -1}Code language: CSS (css)
      

top 和 bottom 的值保持不变,但 left 和 right 的值被更新了。并且只有 C 和 D 需要缩放,才能与 A 和 B 实现水平对称。

拖动下方演示中的元素,以理解不同的区域。(你可以将其与第一篇文章的演示进行对比。)

无论锚点位置配置如何,重要的要求是我们始终只有一个可见区域。创建这些形状时的另一个要求是区域之间的连续性。

连续性?什么?!

如果我们忘掉实现细节,去玩不同的演示,看起来就像是有一条独特的连接线连接着两个元素。实际上我们有四条,但多亏了“连续性”效果,这个错觉完美无缺。

这是之前的示例,我使用了不同的颜色:

你可以清楚地看到我们有四条不同的连接,但当从一条切换到另一条时,它们共享相同的位置/形状来创造连续性效果,让它看起来像是一条。这就是为什么我总是选择直线来进行切换,因为这是最简单的选项。

对于这个新的连接,我们需要在两条曲线之间实现连续性!


        .link :is(a,c):before {
  border-shape: shape(from 0   0,curve to 50% 50% with 150px 0,smooth to 100% 100%) padding-box;
}
.link :is(b,d):before {
  border-shape: shape(from 100% 0,curve to 50% 50% with calc(100% + 150px) 0,smooth to 0   100%) padding-box;
}Code language: CSS (css)
      

A 和 C 仍然共享相同的形状(B 和 D 也是如此)。

看到了吗?这个错觉完美无缺!在我们于 A 和 B 之间切换的那一刻,两个区域中的曲线是相同的。

让我们添加一个条件,使得垂直距离较小时线条变为直线:


        .link *:before {
  border-shape: shape(from 0 0,curve to 50% 50% with if(style(100cqh < 100px): 0;else: 150px;) 0,smooth to 100% 100%) padding-box;
}
.link :is(b,d):before {
  border-shape: shape(from 100% 0,curve to 50% 50% with if(style(100cqh < 100px): 100%;else: calc(100% + 150px);) 0,smooth to 0 100%) padding-box;
}Code language: CSS (css)
      

什么?shape() 里面用 if()?!

是的,我们可以做到。整个 if() 块会解析出一个值,所以你可以把它放在任何你想要的位置,只要返回的值在那里是有效的。由于我只想更新一个值,把 if() 放进 shape() 里比把两个 shape() 值放进 if() 里更有意思,而且代码更少。

这里再次给出完整演示。

而这只是 10 种不同情况中的一种!是的,我们可以拥有所有可能的连接。

左到左:

右到右:

下到左(或左到下):

下到上(或上到下):

你能完成剩下的吗?把这当作家庭作业。研究我分享的演示,然后尝试一个新的。逻辑始终相同。先根据潜在的对称性确定合适的锚点位置配置。然后绘制形状,同时记住连续性效果。

当然,如果把不同的连接组合到一张图里,会更有趣。

四个分别标有 A、B、C、D 的彩色矩形,由灰色曲线连成一个环,这些曲线从每个块的不同侧边离开并到达
四个分别标有 A、B、C、D 的彩色矩形,由灰色曲线连成一个环,这些曲线从每个块的不同侧边离开并到达

想要更多?

既然我们已经看到这项技术的潜力,那就再推进一步,创建一些炫酷的演示吧!

当两个元素靠近时,一条会弯曲的直线怎么样?

用纯 CSS 做点物理效果!而且如果元素离得太远,线会拉伸(变得更细)。

思路是定义一个静态形状,然后根据宽度调整高度。当宽度变小时,高度增大,反之亦然。

这里有一个演示,可以更好地理解发生了什么。调整元素大小,看看曲线如何表现:

还有,别问我数学;为了找到一些复杂公式的良好近似,我不得不在网上问了两个问题。

让我们试试不一样的东西:锯齿形连接器。这个不太稳健,所以不要把它拉得太远,否则它可能会断。别说我没警告过你!

也许来个双锯齿形?

其中有一些滚动驱动动画,但别担心,我不会用无聊的解释来烦你。享受这些有趣的演示吧!

那弹簧连接器怎么样?这个你可以尽情发挥;它不会断!

你还有别的疯狂连接器想法吗?在评论中分享吧。我随时接受任何 CSS 编码挑战。

结论

我们完成了!希望你喜欢这个系列。又一个充满大量 CSS 乐趣的系列。你还记得创建基本形状曾经是噩梦吗?!现在,我们可以制作带有复杂连接的图表,连 SVG 都不容易做到!

再说一次,我们所做的并不是用于生产环境的东西(至少现在还不是)。把它用于娱乐,用它来创建炫酷的东西,但最重要的是,理解我们用到的特性:锚点定位、shape()、border-shape、if() 条件等。一旦它们被广泛支持,你肯定会在生产环境中使用它们。

  • 两个圆、一个箭头与锚点定位
  • 两个圆、一个连接与 border-shape
  • 使用现代 CSS 创建图表和图形

发表回复 取消回复

The translation is machine-generated and individual terms may be off. Nothing is rewritten or cut; the original is authoritative.

Published by Lobsters
This site only summarises and aggregates; copyright stays with the original author. Headlines and summaries follow the source — where they differ, the original wins.
Read the original ↗