如何使用 React 和 React Hooks 构建 SVG 循环进度组件
由Uzochukwu Eddie Odozi撰写✏️ 进度条用于指示桌面或移动设备上的文件上传和下载、页面加载、用户计数等活动。这种视觉表示可以大大增强您的应用程序的用户体验。 在本教程中,我们将演示如何使用 React 从可缩放矢量图形 (SVG) 创建一个简单、可定制、易于使用的圆形进度条组件。我们将不使用外部依赖项来做到这一点。 以下是循环进度组件的外观: [](https://res.cl
由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中参考本教程的完整源代码。
让我们潜入!
入门
在开始之前,我们必须先创建一个 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
文件中,删除除类App
和App-header
之外的所有 CSS 样式。您可以将两个类名都更改为小写。在App.js
组件文件中,删除 header 元素的内容并将其更改为 div。
<div className="app">
<div className="app-header">
</div>
</div>
进入全屏模式 退出全屏模式
create-react-app
将App.js
内部的组件创建为功能组件。您可以使用函数的默认定义或将其更改为箭头函数。
进度组件设置
要创建进度组件,请创建一个名为progress
的文件夹并添加两个文件ProgressBar.js
和ProgressBar.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
元素可以接受许多属性;我们将添加width
和height
。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>
元素,它绘制一个由文本组成的图形元素。我们还将添加属性x
和y
,它们表示文本的 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 传入到循环进度组件中。其他属性,例如radius
和circumference
,是根据提供的 props 计算得出的。
将 props 属性传递给箭头函数并解构这五个属性。
const ProgressBar = (props) => {
const {
size,
progress,
strokeWidth,
circleOneStroke,
circleTwoStroke,
} = props;
...
}
进入全屏模式 退出全屏模式
接下来,计算圆的半径和周长。添加一个名为center
的新变量,并将其值设置为作为 props 传入的大小的一半。该值将用于圆心的cx
和cy
坐标。
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-dasharray
和stroke-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 钩子:useState
、useEffect
和useRef
。useState
根据作为道具传递的进度值更新stroke-dashoffset
,并在
useEffect
挂钩。useRef
钩子将用于获取对第二个圆圈的引用,然后
向圆圈添加 CSS 过渡属性。
从 React 导入useState
、useEffect
和useRef
挂钩。
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% 的进展:
[](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% 的进度:
[](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
添加一些过渡,我们将使用已定义的useRef
。useRef
钩子让我们可以访问 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
并创建一个名为progress
的useState
属性。
const [progress, setProgress] = useState(0);
进入全屏模式 退出全屏模式
创建一个useState
颜色属性。
const [color, setColor] = useState('');
进入全屏模式 退出全屏模式
添加带有十六进制代码的颜色数组。你可以设置任何你想要的颜色。
const colorArray = ['#7ea9e1', "#ed004f", "#00fcf0", "#d2fc00", "#7bff00", "#fa6900"];
进入全屏模式 退出全屏模式
将从数组中选择一种随机颜色并显示在循环进度组件上。
使用progress
和color
属性更新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
方法添加随机颜色。
请注意,使用随机颜色数组是可选的。您可以为circleOneStroke
和circleTwoStroke
道具设置任意两种颜色。
[](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 钩子(例如useState
、useEffect
和useRef
)创建自定义圆形进度条有了很好的理解。
在GitHub repo中查看本教程的完整源代码。
如果你喜欢看我编码,你可以在这里查看这个YouTube 视频。
全面了解生产 React 应用程序
调试 React 应用程序可能很困难,尤其是当用户遇到难以重现的问题时。如果您对监控和跟踪 Redux 状态、自动显示 JavaScript 错误以及跟踪缓慢的网络请求和组件加载时间感兴趣,试试 LogRocket。
[](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 博客上。
更多推荐
所有评论(0)