由Uzochukwu Eddie Odozi撰写✏️

进度条用于指示桌面或移动设备上的文件上传和下载、页面加载、用户计数等活动。这种视觉表示可以大大增强您的应用程序的用户体验。

在本教程中,我们将演示如何使用 React 从可缩放矢量图形 (SVG) 创建一个简单、可定制、易于使用的圆形进度条组件。我们将不使用外部依赖项来做到这一点。

以下是循环进度组件的外观:

[循环进度组件](https://res.cloudinary.com/practicaldev/image/fetch/s--xqpM31H2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://i1.wp .com/blog.logrocket.com/wp-content/uploads/2020/04/circular-progress-component-complete.gif%3Fresize%3D720%252C404%26ssl%3D1)

您可以在GitHub repo中参考本教程的完整源代码。

让我们潜入!

LogRocket 免费试用横幅

入门

在开始之前,我们必须先创建一个 React 应用程序。我们将使用create-react-app和 npx 来创建我们的应用程序。我假设你的计算机上安装了 Node.js。

打开终端或命令提示符,导航到要添加项目的目录并键入以下命令。

npx create-react-app react-progress-bar

您可以使用您选择的任何 IDE 打开项目。

create-react-app创建一个src目录。这是包含我们应用程序的入口组件 (App.js) 以及将创建其他组件的目录。删除index.css文件内容,添加:

body {
  margin: 0;
}

进入全屏模式 退出全屏模式

App.css文件中,删除除类AppApp-header之外的所有 CSS 样式。您可以将两个类名都更改为小写。在App.js组件文件中,删除 header 元素的内容并将其更改为 div。

<div className="app">
    <div className="app-header">
    </div>
</div>

进入全屏模式 退出全屏模式

create-react-appApp.js内部的组件创建为功能组件。您可以使用函数的默认定义或将其更改为箭头函数。

进度组件设置

要创建进度组件,请创建一个名为progress的文件夹并添加两个文件ProgressBar.jsProgressBar.css。在ProgressBar.js文件中,创建箭头函数ProgressBar并默认导出函数。将父元素设置为Fragment(从 React 导入)或空标签。

基本 SVG

根据W3 Schools,可缩放矢量图形 (SCG) 用于为 Web 定义基于矢量图的图形。

添加到进度条组件的第一个元素是<svg>元素标签,它定义了坐标系和视口的容器。

import React from 'react';
import './ProgressBar.css';
const ProgressBar = () => {
    return (
        <>
            <svg>

            </svg>
        </>
    );
}
export default ProgressBar;

进入全屏模式 退出全屏模式

svg元素可以接受许多属性;我们将添加widthheight。SVG 容器的宽度和高度将是动态的,因此我们将两者都添加为道具。

return (
    <>
        <svg className="svg" width={} height={}>

        </svg>
    </>
);

进入全屏模式 退出全屏模式

在添加的<svg>元素内,放置一个<circle>标记以创建一个圆圈。在<circle>元素中,声明圆的半径r和圆心的 x 坐标 (cx) 和 y 坐标 (cy)。

此外,我们将定义圆的笔触(颜色)和笔触宽度。我将定义两个单独的<circle>元素:

<svg className="svg" width={} height={}>
    <circle
        className="svg-circle-bg"
        stroke={}
        cx={}
        cy={}
        r={}
        strokeWidth={}
    />
    <circle
        className="svg-circle"
        stroke={}
        cx={}
        cy={}
        r={}
        strokeWidth={}
    />
</svg>

进入全屏模式 退出全屏模式

第一个圆形元素显示内圈,而第二个元素放置在第一个元素的顶部,以根据计算的百分比显示进度颜色。

接下来,添加一个<text></text>元素,它绘制一个由文本组成的图形元素。我们还将添加属性xy,它们表示文本的 x 和 y 起点。

<svg className="svg" width={} height={}>
    ...
    ...
    <text className="svg-circle-text" x={}  y={}>
        ...
    </text>
</svg>

进入全屏模式 退出全屏模式

将以下 CSS 样式添加到ProgressBar.css文件中,并将其导入到组件中。

.svg {
    display: block;
    margin: 20px auto;
    max-width: 100%;
}

.svg-circle-bg {
    fill: none;
}

.svg-circle {
    fill: none;
}
.svg-circle-text {
   font-size: 2rem;
    text-anchor: middle;
    fill: #fff;
    font-weight: bold;
}

进入全屏模式 退出全屏模式

正如你所看到的,我们没有太多的 CSS 样式。进度条元素将包含向元素添加一些样式的属性。让我们仔细看看。

进度组件道具

进度条组件接受五个道具:

1.size— SVG 的全宽和全高

2.progress——循环进度值

3.strokeWidth— 圆圈的宽度(粗细)

4.circleOneStroke— 第一个圆圈的描边颜色

5.circleTwoStroke— 第二个圆圈的笔触颜色

这些属性在使用时会作为 props 传入到循环进度组件中。其他属性,例如radiuscircumference,是根据提供的 props 计算得出的。

将 props 属性传递给箭头函数并解构这五个属性。

const ProgressBar = (props) => {
    const { 
        size, 
        progress, 
        strokeWidth, 
        circleOneStroke, 
        circleTwoStroke,
    } = props;
    ...
}

进入全屏模式 退出全屏模式

接下来,计算圆的半径和周长。添加一个名为center的新变量,并将其值设置为作为 props 传入的大小的一半。该值将用于圆心的cxcy坐标。

const center = size / 2;

进入全屏模式 退出全屏模式

路径的半径被定义在中间,所以为了让路径完美地适合 viewBox,我们需要从一半大小(直径)中减去strokeWidth的一半。圆的周长是2 * π * r

const radius = size / 2 - strokeWidth / 2;
const circumference = 2 * Math.PI * radius;

进入全屏模式 退出全屏模式

将道具和半径添加到 SVG 和圆圈。

<svg className="svg" width={size} height={size}>
    <circle
        className="svg-circle-bg"
        stroke={circleOneStroke}
        cx={center}
        cy={center}
        r={radius}
        strokeWidth={strokeWidth}
    />
    <circle
        className="svg-circle"
        stroke={circleTwoStroke}
        cx={center}
        cy={center}
        r={radius}
        strokeWidth={strokeWidth}
    />
    <text className="svg-circle-text" x={center}  y={center}>
        {progress}%
    </text>
</svg>

进入全屏模式 退出全屏模式

转到App.js文件并导入ProgressBar组件。在类名app-header的 div 元素中添加组件。

const App = () => {
    return (
        <div className="app">
            <div className="app-header">
                <ProgressBar />
            </div>
        </div>
    );
}

进入全屏模式 退出全屏模式

回到ProgressBar.js文件。ProgressBar组件需要在其组件内定义道具。

<ProgressBar 
    progress={50}
    size={500}
    strokeWidth={15}
    circleOneStroke='#7ea9e1'
    circleTwoStroke='#7ea9e1'
/>

进入全屏模式 退出全屏模式

用户可以指定属性的值。稍后,将通过单击按钮和输入来更新进度值。circleTwoStroke值将从一组颜色中随机选择。

[循环进度组件笔划](https://res.cloudinary.com/practicaldev/image/fetch/s--Prn13H0x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2. wp.com/blog.logrocket.com/wp-content/uploads/2020/04/circular-progress-component-strokes.png%3Fresize%3D720%252C378%26ssl%3D1)

使用 SVG 时,有一些方法可以控制笔画的渲染方式。我们来看看stroke-dasharraystroke-dashoffset

stroke-dasharray使您能够控制破折号的长度和每个破折号之间的间距。基本上,它定义了用于绘制形状轮廓的破折号和间隙的模式——在本例中为圆形。

与其创建多个破折号,我们可以创建一个大破折号来环绕整个圆圈。我们将使用我们之前计算的周长来执行此操作。stroke-dashoffset将确定渲染开始的位置。

第二个圆圈将显示0到100之间的进度值。将下面的属性添加到第二个圆圈

strokeDasharray={circumference}

进入全屏模式 退出全屏模式

请注意,我们使用的是strokeDasharray而不是stroke-dasharray。在 react 中,以-分隔的 css 属性在组件内部使用时通常以驼峰命名方式编写。

...
<circle
    className="svg-circle"
    stroke={circleTwoStroke}
    cx={center}
    cy={center}
    r={radius}
    strokeWidth={strokeWidth}
    strokeDasharray={circumference}
/>
...

进入全屏模式 退出全屏模式

我们将使用三个不同的 React 钩子:useStateuseEffectuseRefuseState根据作为道具传递的进度值更新stroke-dashoffset,并在

useEffect挂钩。useRef钩子将用于获取对第二个圆圈的引用,然后

向圆圈添加 CSS 过渡属性。

从 React 导入useStateuseEffectuseRef挂钩。

import React, { useEffect, useState, useRef } from 'react';

进入全屏模式 退出全屏模式

在箭头函数内创建一个新的useState属性并将其默认值设置为零。

const [offset, setOffset] = useState(0);

进入全屏模式 退出全屏模式

在第二个圆圈上,添加一个ref属性,然后在useState属性之后创建一个新变量。

...
<circle
    ...
    ref={circleRef}
    ...
/>
...

进入全屏模式 退出全屏模式

const circleRef = useRef(null);

进入全屏模式 退出全屏模式

circleRef属性将产生对第二个圆的引用,然后我们可以在 DOM 上更新它的样式。

接下来,添加一个useEffect方法

useEffect(() => {

}, []);

进入全屏模式 退出全屏模式

useEffect挂钩内,使用以下公式计算进度的位置:

((100 - progress) / 100) * circumference;

进入全屏模式 退出全屏模式

回想一下,周长已经计算过了,进度是用户设置的道具值。

useEffect(() => {
    const progressOffset = ((100 - progress) / 100) * circumference;
    setOffset(progressOffset);
}, [setOffset, circumference, progress, offset]);

进入全屏模式 退出全屏模式

数组中的属性是依赖项,因此必须添加到 useEffect 数组中。

计算progressOffset后,使用setOffset方法更新offset

添加到第二个圆圈:

...
<circle
    ...
    strokeDashoffset={offset}
    ...
/>
...

进入全屏模式 退出全屏模式

它应该看起来像下面的屏幕截图。

70% 的进展:

[显示 70% 进度的循环进度组件](https://res.cloudinary.com/practicaldev/image/fetch/s--XYMW87Nu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https:/ /i0.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/circular-progress-component-70-percent.png%3Fresize%3D720%252C378%26ssl%3D1)

30% 的进度:

[显示 30% 进度的循环进度组件](https://res.cloudinary.com/practicaldev/image/fetch/s---ojtVmvR---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https: //i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/circular-progress-component-30-percent.png%3Fresize%3D720%252C378%26ssl%3D1)

为了向stroke-dashoffset添加一些过渡,我们将使用已定义的useRefuseRef钩子让我们可以访问 DOM 上元素的current属性,这使我们能够访问 style 属性。我们将把这个过渡放在useEffect钩子中,以便在进度值更改时立即渲染它。

setOffset方法下方和useEffect挂钩内,添加:

circleRef.current.style = 'transition: stroke-dashoffset 850ms ease-in-out;';

进入全屏模式 退出全屏模式

circleRef是为useRef定义的变量,我们可以访问它的 current 和 style 属性。要查看更改,请重新加载浏览器并观察转换是如何发生的。

我们现在有了进度条组件。让我们为组件添加一些 prop-types。

import PropTypes from 'prop-types';

进入全屏模式 退出全屏模式

这会将 prop-types 定义放在导出默认值之前。

ProgressBar.propTypes = {
    size: PropTypes.number.isRequired,
    progress: PropTypes.number.isRequired,
    strokeWidth: PropTypes.number.isRequired,
    circleOneStroke: PropTypes.string.isRequired,
    circleTwoStroke: PropTypes.string.isRequired
}

进入全屏模式 退出全屏模式

这些属性被定义为必需属性。如果您愿意,可以向组件添加更多属性。

您的ProgressBar功能组件应如下所示:

import React, { useEffect, useState, useRef } from 'react';

import PropTypes from 'prop-types';
import './ProgressBar.css';

const ProgressBar = props => {
    const [offset, setOffset] = useState(0);
    const circleRef = useRef(null);
    const { 
        size, 
        progress, 
        strokeWidth, 
        circleOneStroke, 
        circleTwoStroke,
    } = props;

    const center = size / 2;
    const radius = size / 2 - strokeWidth / 2;
    const circumference = 2 * Math.PI * radius;

    useEffect(() => {
        const progressOffset = ((100 - progress) / 100) * circumference;
        setOffset(progressOffset);
        circleRef.current.style = 'transition: stroke-dashoffset 850ms ease-in-out;';
    }, [setOffset, circumference, progress, offset]);

    return (
        <>
            <svg
                className="svg"
                width={size}
                height={size}
            >
                <circle
                    className="svg-circle-bg"
                    stroke={circleOneStroke}
                    cx={center}
                    cy={center}
                    r={radius}
                    strokeWidth={strokeWidth}
                />
                <circle
                    className="svg-circle"
                    ref={circleRef}
                    stroke={circleTwoStroke}
                    cx={center}
                    cy={center}
                    r={radius}
                    strokeWidth={strokeWidth}
                    strokeDasharray={circumference}
                    strokeDashoffset={offset}
                />
                <text 
                    x={`${center}`} 
                    y={`${center}`} 
                    className="svg-circle-text">
                        {progress}%
                </text>
            </svg>
        </>
    )
}

ProgressBar.propTypes = {
    size: PropTypes.number.isRequired,
    progress: PropTypes.number.isRequired,
    strokeWidth: PropTypes.number.isRequired,
    circleOneStroke: PropTypes.string.isRequired,
    circleTwoStroke: PropTypes.string.isRequired
}

export default ProgressBar;

进入全屏模式 退出全屏模式

生成随机进度值

要查看应用于进度的转换,我们将创建一个输入字段以使用户能够更改进度值,并创建一个按钮以添加随机进度值。

首先将以下 CSS 样式添加到App.css文件中。

button {
  background: #428BCA;
  color: #fff;
  font-size: 20px;
  height: 60px;
  width: 150px;
  line-height: 60px;
  margin: 25px 25px;
  text-align: center;
  outline: none;
}

input { 
  border: 1px solid #666; 
  background: #333; 
  color: #fff !important; 
  height: 30px;
  width: 200px;
  outline: none !important; 
  text-align: center;
  font-size: 16px;
  font-weight: bold;
}

input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

input[type=number] {
  -moz-appearance: textfield;
}

h1 { 
  margin: 0;
  text-transform: uppercase;
  text-shadow: 0 0 0.5em #fff;
  font-size: 46px;
  margin-bottom: 20px;
}

进入全屏模式 退出全屏模式

样式是按钮、输入和 h1 元素的基本样式。接下来,将一些元素添加到类名为app-header的 div 中。

<h1>SVG Circle Progress</h1>
<ProgressBar 
    progress={50}
    size={500}
    strokeWidth={15}
    circleOneStroke='#7ea9e1'
    circleTwoStroke='#7ea9e1'
/>
<p>
    <input 
        type="number"
        name="percent" 
        placeholder="Add Progress Value"
        onChange={}
    />
</p>
<button>
    Random
</button>

进入全屏模式 退出全屏模式

这会添加 sheader标签、带输入的p标签和一个按钮。让我们将onChange method添加到输入中。

...
...
<p>
    <input 
        type="number"
        name="percent" 
        placeholder="Add Progress Value"
        onChange={onChange}
    />
</p>
...

进入全屏模式 退出全屏模式

const onChange = e => {

}

进入全屏模式 退出全屏模式

onChange方法内部,会选择进度值和随机颜色,

属性更新。导入useState并创建一个名为progressuseState属性。

const [progress, setProgress] = useState(0);

进入全屏模式 退出全屏模式

创建一个useState颜色属性。

const [color, setColor] = useState('');

进入全屏模式 退出全屏模式

添加带有十六进制代码的颜色数组。你可以设置任何你想要的颜色。

const colorArray = ['#7ea9e1', "#ed004f", "#00fcf0", "#d2fc00", "#7bff00", "#fa6900"];

进入全屏模式 退出全屏模式

将从数组中选择一种随机颜色并显示在循环进度组件上。

使用progresscolor属性更新ProgressBar组件。

<ProgressBar 
    progress={progress}
    size={500}
    strokeWidth={15}
    circleOneStroke='#7ea9e1'
    circleTwoStroke={color}
/>

进入全屏模式 退出全屏模式

添加一个从colorArray中获取随机颜色的方法。

const randomColor = () => {
    return colorArray[Math.floor(Math.random() * colorArray.length)];
}

进入全屏模式 退出全屏模式

将进度组件的最大值设置为 100,将最小值设置为 0。如果

输入值小于零,进度设置为零。如果大于 100,

进度设置为 100。

if (e.target.value) {
    if (e.target.value > 100) {
        progress = 100;
    }
    if (e.target.value < 0) {
        progress = 0;
    }
    setProgress(progress);
}

进入全屏模式 退出全屏模式

setProgress方法将更新进度值。在setProgress下方添加randomColor方法,并使用setColor更新颜色变量。

...
const randomProgressColor = randomColor();
setColor(randomProgressColor);

进入全屏模式 退出全屏模式

如果你尝试一下,你会发现它可以工作,但是如果输入字段为空,它仍然会保留一些旧的

价值。这不是我们想要的行为。为了解决这个问题,我将在onChange中添加一个 else 语句,并将进度值设置为零。

if (e.target.value) {
    ...
} else {
    setProgress(0);
}

进入全屏模式 退出全屏模式

每当输入字段被清除或为空时,这会将进度值设置为零。

[具有随机值的循环进度组件](https://res.cloudinary.com/practicaldev/image/fetch/s--uyJ2a2ky--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https:// i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/circular-progress-component-random-values.gif%3Fresize%3D720%252C404%26ssl%3D1)

随机按钮功能

在按钮上添加一个onClick方法,并创建一个函数来随机设置进度值。

<button onClick={randomProgressValue}>
    Random
</button>

进入全屏模式 退出全屏模式

创建一个名为randomProgressValue的方法。

const randomProgressValue = () => {
}

进入全屏模式 退出全屏模式

首先,使用Math.random()获取 0 到 100 之间的随机值,并使用setProgress方法设置其值。调用randomColor方法并更新颜色值。

const randomProgressValue = () => {
    const progressValue = Math.floor(Math.random() * 101);
    setProgress(progressValue);
    const randomProgressColor = randomColor();
    setColor(randomProgressColor);
}

进入全屏模式 退出全屏模式

每当单击按钮时,都会设置一个随机进度值,并使用setColor方法添加随机颜色。

请注意,使用随机颜色数组是可选的。您可以为circleOneStrokecircleTwoStroke道具设置任意两种颜色。

[具有随机按钮功能的循环进度组件](https://res.cloudinary.com/practicaldev/image/fetch/s--yROAL-XP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https ://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/circular-progress-component-random-button.gif%3Fresize%3D720%252C404%26ssl%3D1)

结论

您现在应该对如何使用 React 钩子(例如useStateuseEffectuseRef)创建自定义圆形进度条有了很好的理解。

在GitHub repo中查看本教程的完整源代码。

如果你喜欢看我编码,你可以在这里查看这个YouTube 视频。


全面了解生产 React 应用程序

调试 React 应用程序可能很困难,尤其是当用户遇到难以重现的问题时。如果您对监控和跟踪 Redux 状态、自动显示 JavaScript 错误以及跟踪缓慢的网络请求和组件加载时间感兴趣,试试 LogRocket。

[Alt](https://res.cloudinary.com/practicaldev/image/fetch/s--NrgqgMPv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev. s3.amazonaws.com/i/eq752g8qhbffxt3hp9t4.png)

LogRocket就像一个用于 Web 应用程序的 DVR,几乎可以记录您的 React 应用程序上发生的所有事情。无需猜测问题发生的原因,您可以汇总并报告问题发生时应用程序所处的状态。 LogRocket 还监控您的应用程序的性能,并使用客户端 CPU 负载、客户端内存使用情况等指标进行报告。

LogRocket Redux 中间件包为您的用户会话增加了一层额外的可见性。 LogRocket 记录来自 Redux 存储的所有操作和状态。

现代化调试 React 应用程序的方式 —免费开始监控。


如何使用 React 和 React Hooks构建 SVG 循环进度组件的帖子首先出现在LogRocket 博客上。

Logo

React社区为您提供最前沿的新闻资讯和知识内容

更多推荐