发布于 2026-01-06 6 阅读
0

在 React DEV 的全球展示与讲述挑战赛中,通过 Mux 呈现的“展示你的项目!”来创造一个进步的循环。

在 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>


Enter fullscreen mode Exit fullscreen mode

我们已经取得了不错的开端,一些数值是硬编码的——特别是圆的半径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>


Enter fullscreen mode Exit fullscreen mode

我之前提到过一些很棒的 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>
  );
};


Enter fullscreen mode Exit fullscreen mode

实际上这只是个起点,因为其中仍然包含一些硬编码值——我们是否要将半径固定为 0.05 70,描边宽度固定为 0.05 2rem,或者将圆的大小固定为 0.05 200?我想可能不需要,现在这些都由我们自己控制了——我在所有需要添加动态值的地方都保留了花括号。目前,该组件仅接受百分比和颜色参数,但它也可以接受描边宽度、半径、圆角等参数。

您可以查看最终代码,其中包含一些示例,我添加了一些颜色,并使用stroke-linecap="round"以下方法将末端变圆;我还添加了一个“随机化”按钮,以便您可以查看其实际效果。

文章来源:https://dev.to/jackherizsmith/making-a-progress-circle-in-react-3o65