使用方法
CircleProgress组件主要的特别功能就是根据不同的区间展示不同的颜色,区间可定义。
<CircleProgress
description={'小明的成绩'}
// descriptionStyle={{ color: 'blue', fontWeight: 900 }}
percent={95}
unit={'分'}
percentStyle={{ fontSize: '25px' }}
// color='#4CAF50'
color={[
{ color_value: '#4CAF50', color_range: [0.8, 1] },
{ color_value: '#F4D84E', color_range: [0.6, 0.8] },
{ color_value: '#F50C0C', color_range: [0, 0.6] },
]}
trackColor="#EEEEEE"
size="10rem"
thickness={8}
dur={0.1}></CircleProgress>
源码展示
import { CSSProperties, FC, ReactNode, useEffect, useState, useContext, memo } from 'react'
import classNames from 'classnames'
import './style.scss'
import UtilsClass from '../../utils/UtilsClass'
import context from '../../utils/context'
import { computedColor } from './utils'
import { animation } from '@/utils/tools/animation'
// strokeDasharray 的第一个值(填充部分长度)表示进度环的“实线”部分,应该有多长,而第二个值(圆周长)表示整个进度环的周长。
const getRingPercent = (percent: number, r: number) => {
const perimeter = Math.PI * 2 * r
return (percent / 100) * perimeter + ' ' + perimeter
}
interface ProgressCircleProps {
className?: string
style?: CSSProperties
children?: ReactNode
percent?: number
// 圆环颜色
color?: string
// 圆环底色
trackColor?: string
// 圆环尺寸
size?: string | number
// 圆环厚度
thickness?: number
// 动画持续时间
dur?: number
description?: string
percentStyle?: {}
descriptionStyle?: {}
unit: string
}
export const CircleProgress: FC<ProgressCircleProps> = (props) => {
let obj = useContext(context)
const {
dur,
className,
style,
children,
percent = 0,
color,
trackColor,
size,
thickness = 4,
description,
percentStyle,
descriptionStyle,
unit,
...restProps
} = props
const [finalDashArray, setFinalDashArray] = useState('')
const [trailStyle, setTrailStyle] = useState({})
const [trackStyle, setTrackStyle] = useState({})
const [animatedPercent, setAnimatedPercent] = useState(0)
const radius = 50 - thickness / 2
const perimeter = Math.PI * 2 * radius
const progressClass = classNames('progress-circle', className)
const progressStyle = {
width: size,
height: size,
...style,
}
const initAnimation = () => {
const finalDash = getRingPercent(percent, radius)
const renderValue = computedColor(color, percent) || color
setTrackStyle({
stroke: trackColor,
strokeWidth: thickness,
r: radius,
})
setTrailStyle({
stroke: renderValue,
strokeDasharray: finalDash,
strokeWidth: thickness,
r: radius,
})
}
useEffect(() => {
setFinalDashArray(getRingPercent(percent, radius))
animation(dur * 20000, 0, percent, (val) => setAnimatedPercent(val))
}, [percent, radius, dur])
useEffect(() => {
initAnimation()
}, [percent])
const animateFrom = `0 ${perimeter}`
const animateTo = `${(percent / 100) * perimeter} ${perimeter}`
useEffect(() => {
const finalDash = getRingPercent(percent, radius)
setFinalDashArray(finalDash)
}, [percent, radius])
const renderPercentStyle = Object.assign({}, percentStyle, { position: 'absolute' })
const renderDescriptionStyle = Object.assign({}, descriptionStyle, {
width: size,
display: description ? 'block' : 'none',
})
return (
<>
<div {...restProps} className={progressClass} style={progressStyle}>
<svg viewBox="0 0 100 100" className="progress-circle-graph">
<circle cx="50" cy="50" fill="none" className="progress-circle-track" style={trackStyle} />
<circle cx="50" cy="50" fill="none" className="progress-circle-trail" style={trailStyle}>
<animate
attributeName="stroke-dasharray"
begin="0s"
dur={dur + 's'} // 动画持续时间,例如1秒
from={animateFrom}
to={animateTo}
fill="freeze"
/>
</circle>
</svg>
<div style={renderPercentStyle}>
{Math.round(animatedPercent)}
{unit}
</div>
</div>
<div className="progress_circle_description" style={renderDescriptionStyle}>
{description}
</div>
</>
)
}
export default memo(CircleProgress)
utils
type colorProps = [
{ color_value: string; color_range: number[] },
{ color_value: string; color_range: number[] },
{ color_value: string; color_range: number[] },
]
type info_type = { color_value: string; color_range: number[] }
/**
*计算值是否在区间内返回boolean
* @param value
* @param range_arr
* @returns boolean
*/
export const isInRange = (value: number, range_arr: number[]): boolean => {
const [min, max] = range_arr
return value >= min && value <= max
}
/**
* 根据不同值的范围显示不同的颜色
* @param color
* @param percent
* @returns string
*/
export const computedColor = (color: colorProps, percent: number) => {
if (color && Array.isArray(color)) {
let renderValueColor = color
.map((info: info_type) => {
if (isInRange(percent / 100, info.color_range)) {
return info.color_value
}
})
.filter((item) => item !== undefined)
return renderValueColor[0]
}
if (!color) {
throw new Error('CircleProgress组件的color属性未定义')
}
}
样式文件
.progress-circle {
position: relative;
z-index: 0;
display: flex;
justify-content: center;
align-items: center;
}
.progress-circle-graph {
// position: absolute;
// top: 0;
// left: 0;
// z-index: -1;
}
.progress-circle-trail {
stroke-linecap: round;
transform: rotate(-90deg);
transform-origin: center center;
transition: stroke-dasharray 3s ease, stroke 0.3s;
}
.progress_circle_description {
text-align: center;
}