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

使用现代 CSS 创建图表和图形
在两篇详细的文章(希望您已阅读!)之后,我们拥有了一种绝佳的技术,可以使用 border-shape 和 shape() 来用线条连接两个元素,并且可以塑造线条形状。
在本文中,我们将探索更多常用于创建图表和图形的连接类型。当然,我们无法涵盖所有类型,但这项技术是通用的,因此如果您理解透彻,就可以轻松调整代码来创建自己的连接。
- 两个圆、一个箭头和锚点定位
- 两个圆、一个连接和 border-shape
- 使用现代 CSS 创建图表和图形
在开始之前,请允许我提醒您,目标并不是发明一种新的或更好的使用 CSS 创建图表的方法。这仍然是一个实验,旨在探索现代特性并创造仅使用 CSS 无法实现(或非常困难)的东西。这也可以作为概念验证,证明使用 CSS 确实是可行的。如果可行,它可以带来更好、更直观的实现。
闲话少说,让我们开始吧!
在撰写本文时,只有 Chrome 和 Edge 完全支持我们将要使用的特性。
动态连接
第一种连接类型根据元素位置和形状进行调整。它类似于我们在之前文章中做的,但这次我们不仅连接中心点,还考虑侧边。
让我们从一个演示开始:
如果您拖动元素,您会注意到连接会考虑最接近的侧边来连接两个元素,如果它们靠得太近,则会连接它们的中心。一个动态连接!
如果您仔细观察,您会注意到我们有四种不同的位置,因此有四种不同的形状(一条曲线和三条直线)。

您忘记了一些情况吗?还有更多位置!
是的,但别忘了锚点位置配置。我们有四个区域,一次只显示一个,它们都是对称的,所以缩放变换给出了其余的部分。如果这最后一句话听起来奇怪,请回到第一篇文章复习一下。
形状与我们上一篇文章中使用的相同,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 和 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 种不同情况中的一种!是的,我们可以拥有所有可能的连接。
左到左:
右到右:
下到左(或左到下):
下到上(或上到下):
你能完成剩下的吗?把这当作家庭作业。研究我分享的演示,然后尝试一个新的。逻辑始终相同。先根据潜在的对称性确定合适的锚点位置配置。然后绘制形状,同时记住连续性效果。
当然,如果把不同的连接组合到一张图里,会更有趣。

想要更多?
既然我们已经看到这项技术的潜力,那就再推进一步,创建一些炫酷的演示吧!
当两个元素靠近时,一条会弯曲的直线怎么样?
用纯 CSS 做点物理效果!而且如果元素离得太远,线会拉伸(变得更细)。
思路是定义一个静态形状,然后根据宽度调整高度。当宽度变小时,高度增大,反之亦然。
这里有一个演示,可以更好地理解发生了什么。调整元素大小,看看曲线如何表现:
还有,别问我数学;为了找到一些复杂公式的良好近似,我不得不在网上问了两个问题。
让我们试试不一样的东西:锯齿形连接器。这个不太稳健,所以不要把它拉得太远,否则它可能会断。别说我没警告过你!
也许来个双锯齿形?
其中有一些滚动驱动动画,但别担心,我不会用无聊的解释来烦你。享受这些有趣的演示吧!
那弹簧连接器怎么样?这个你可以尽情发挥;它不会断!
你还有别的疯狂连接器想法吗?在评论中分享吧。我随时接受任何 CSS 编码挑战。
结论
我们完成了!希望你喜欢这个系列。又一个充满大量 CSS 乐趣的系列。你还记得创建基本形状曾经是噩梦吗?!现在,我们可以制作带有复杂连接的图表,连 SVG 都不容易做到!
再说一次,我们所做的并不是用于生产环境的东西(至少现在还不是)。把它用于娱乐,用它来创建炫酷的东西,但最重要的是,理解我们用到的特性:锚点定位、shape()、border-shape、if() 条件等。一旦它们被广泛支持,你肯定会在生产环境中使用它们。
- 两个圆、一个箭头与锚点定位
- 两个圆、一个连接与 border-shape
- 使用现代 CSS 创建图表和图形