在 React 中创建进度圆
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
当我需要一些简单但又不知道如何快速实现的功能时,很容易会想到找个现成的库。但每个库的内部机制究竟是怎样的呢?我该如何让它实现我想要的功能,而不是它自带的二十多种其他功能呢?我觉得有时候阅读文档、寻找变通方法所花费的时间,可能比从头开始自己实现还要长,而且最终实现出来的代码更轻量级,也更容易进行调整。
我最近想做一个进度圆饼图/饼图。成品大概长这样:
以下是我在 React 中的实现方法。本文旨在鼓励大家用自己的方式做事,而不是使用别人现成的组件,所以我很想知道你们会如何改进或添加哪些功能,欢迎在评论区留言。
SVG
我非常喜欢 SVG,它们太棒了。它们几乎拥有你想要的所有属性,而且不像某些 CSS 属性那样,它们可以在所有主流浏览器上运行。所以实际上我们可以完全不用 CSS 来实现这个功能。由于这是一个动态组件,需要接收百分比值,我们将使用 JavaScript 来进行所有计算。
我们将绘制两个圆,一个(蓝色)叠在另一个(浅灰色)之上。我们将使用 SVG 的 stroke-dasharray 属性来设置虚线的长度,以及 stroke-dashoffset 属性来设置虚线相对于其自然起始点的位置。
因此,虚线的长度将是圆的周长——2 * pi * radius而我们需要为蓝色圆调整的偏移量将是圆周长的百分比。当我们想要显示 85% 时,虚线必须从圆周长的 15% 处开始,这样我们就只能看到虚线结束前剩余的 85% 的线条。
<svg width="200" height="200">
<circle r="70" cx="100" cy="100" fill="transparent" stroke="lightgrey" stroke-width="2rem" stroke-dasharray="439.8" stroke-dashoffset="0"></circle>
<circle r="70" cx="100" cy="100" fill="transparent" stroke="blue" stroke-width="2rem" stroke-dasharray="439.8" stroke-dashoffset="66"></circle>
</svg>
我们已经取得了不错的开端,一些数值是硬编码的——特别是圆的半径70、圆的周长439.8以及从 开始的“85%”指示条66。如果你自己尝试一下,你会发现我们实际的位置比预期顺时针旋转了 90 度,而且缺少应该位于中心位置的文本。所以我们可以将这些圆放在一个组中,旋转 -90 度,并添加一些文本。
<svg width="200" height="200">
<g transform="rotate(-90 100 100)">
<circle r="70" cx="100" cy="100" fill="transparent" stroke="lightgrey" stroke-width="2rem" stroke-dasharray="439.8" stroke-dashoffset="0"></circle>
<circle r="70" cx="100" cy="100" fill="transparent" stroke="blue" stroke-width="2rem" stroke-dasharray="439.8" stroke-dashoffset="66">
</circle>
</g>
<text x="50%" y="50%" dominant-baseline="central" text-anchor="middle">85%</text>
</svg>
我之前提到过一些很棒的 SVG 属性,dominant-baseline它们可以text-anchor帮助我们垂直和水平居中文本。用 CSS 实现类似的功能可能会比较麻烦。旋转 SVG 时,我们还可以指定旋转中心——在本例中,旋转中心位于中间100 100。
实际上,这已经为我们提供了文章顶部的进度圆圈,所以我们可以将其迁移到 React 了。
使其成为一个组件
使用 React 可以让我们对所使用的值进行很多动态控制。假设我们想要输入的是百分比,以及进度条显示的颜色。
首先,我们要“清理”输入,确保它是一个可以使用的数字,然后我们可以将 SVG 部分设置为可重用的组件,这样基本上就完成了。
const cleanPercentage = (percentage) => {
const isNegativeOrNaN = !Number.isFinite(+percentage) || percentage < 0; // we can set non-numbers to 0 here
const isTooHigh = percentage > 100;
return isNegativeOrNaN ? 0 : isTooHigh ? 100 : +percentage;
};
const Circle = ({ colour, percentage }) => {
const r = 70;
const circ = 2 * Math.PI * r;
const strokePct = ((100 - percentage) * circ) / 100; // where stroke will start, e.g. from 15% to 100%.
return (
<circle
r={r}
cx={100}
cy={100}
fill="transparent"
stroke={strokePct !== circ ? colour : ""} // remove colour as 0% sets full circumference
strokeWidth={"2rem"}
strokeDasharray={circ}
strokeDashoffset={percentage ? strokePct : 0}
></circle>
);
};
const Text = ({ percentage }) => {
return (
<text
x="50%"
y="50%"
dominantBaseline="central"
textAnchor="middle"
fontSize={"1.5em"}
>
{percentage.toFixed(0)}%
</text>
);
};
const Pie = ({ percentage, colour }) => {
const pct = cleanPercentage(percentage);
return (
<svg width={200} height={200}>
<g transform={`rotate(-90 ${"100 100"})`}>
<Circle colour="lightgrey" />
<Circle colour={colour} percentage={pct} />
</g>
<Text percentage={pct} />
</svg>
);
};
实际上这只是个起点,因为其中仍然包含一些硬编码值——我们是否要将半径固定为 0.05 70,描边宽度固定为 0.05 2rem,或者将圆的大小固定为 0.05 200?我想可能不需要,现在这些都由我们自己控制了——我在所有需要添加动态值的地方都保留了花括号。目前,该组件仅接受百分比和颜色参数,但它也可以接受描边宽度、半径、圆角等参数。
您可以查看最终代码,其中包含一些示例,我添加了一些颜色,并使用stroke-linecap="round"以下方法将末端变圆;我还添加了一个“随机化”按钮,以便您可以查看其实际效果。

