有没有碰到自定义环形进度条的需求呢,如果有该怎么自定义呢,当然可能有人直接就用上万能的 echarts 了,如果项目简单没有用到 echarts 直接引入那么项目会变大不少,并且很多自定义效果其都不满足,综合之下还是自己编写一个最简单,并且还能熟练技能
我们的目的是实现这样一个效果,当然核心是环形渐变进度条,其他的都是装饰
这里使用了 background + mask + conic-gradient + radial-gradient 实现
我们先画一个纯色进度条
首先画的是一个扇形,使用 background + conic-gradient 实现,conic-gradient由于是渐变,因此中间一个百分比一定是要有两个一样但是不同颜色,或者是渐变区间非常小,这样就能看到我们需要的弧形进度了
然后通过 mask + radial-gradient 实现环形 radial-gradient 是一个环形渐变色效果,默认从中间带外圈,但是使用到 mask 后,可以设计形成一个环形效果,具体可以自己尝试,一般50%~60%左右比较好看,这样就可以形成一个圆环了,当然别忘了 borderRadius,这样就是我们要的环形进度条效果了
ps: mask 这个效果不是在所有平台都能正常显示的,如果不是需要中间透明,直接中间覆盖上一个圆背景就完事了
代码很简单,如下所示,加入了一些样式吧
//一个环形进度条,只是一个进度条哈
<div
className="size-[47px] flex justify-center items-center"
style={{
borderRadius: "50%",
//设置渐变区间,为了突然变色,可以一个百分比设置两个颜色,也可以极小差距,例如25% 25.01%
background: `conic-gradient(#fff 25%, #fff 25%, #0482FF 25%, #0482FF 100%)`,
//设置mask,其会形成环形mask
mask: "radial-gradient(transparent 0%, transparent 57%, #000 57%, #000 100%)",
}}
/>
在画一个渐变色进度条,那就是中间多了几个过渡色罢了,我们先还原成这样,改动不大(外面的圈是 border 懒得改了哈)
<div
className="margin-auto size-[196px]"
style={{
borderRadius: "50%",
background: `conic-gradient( #00C0FF 0%, #475FFF 25%, #8F00FF 50%, #3C4F69 50%, #3C4F69 100%)`,
mask: "radial-gradient(transparent, transparent 58%, #000 58%, #000 100%)",
}}
/>
这样上面效果就完成了,过渡色百分比是实际的一半即可
<div
className="margin-auto size-[196px]"
style={{
borderRadius: "50%",
background: `conic-gradient( #00C0FF 0%, #475FFF ${perc/2}%, #8F00FF ${perc}%, #3C4F69 50%, #3C4F69 100%)`,
mask: "radial-gradient(transparent, transparent 58%, #000 58%, #000 100%)",
}}
/>
//更好的 mask,需要调整大小,就先这样,上面的mask径变角落不是半径,所以才出现的百分比不对问题
mask: radial-gradient(circle farthest-side at center,transparent, transparent 80%, #000 80%, #000 100%);
但是上面还是有点问题,就是,不管进度多少,永远都是那么两个渐变色,实际上,百分比少了可能只有第一个颜色那个才更会有这种百分比的感觉,可以背景 + 遮罩方式解决,遮罩颜色为非百分比颜色,百分比的颜色设置透明即可
上面的随着百分比变小,渐变过渡仍然会出现问题,也就是会一直有这两个颜色,实际上百分比低的则只显示右边蓝色才对,因此搞两个就行了,一个正常渲染的条,一个按照进度覆盖即可(前面透明表示进度,后面纯色覆盖即可)
<div className="zwxt-xxjs-circle-bkg size-[240px] relative flex justify-center items-center">
<div
className="absolute margin-auto size-[196px]"
style={{
borderRadius: "50%",
background: `conic-gradient( #00C0FF 0%, #475FFF 50%, #8F00FF 100%)`,
mask: "radial-gradient(transparent 58%, #000 58%)",
}}
/>
<div
className="absolute margin-auto size-[196px]"
style={{
borderRadius: "50%",
background: `conic-gradient(transparent 0%, transparent ${perc}, #3C4F69 ${perc}, #3C4F69 100%)`,
mask: "radial-gradient(transparent 58%, #000 58%)",
}}
/>
...img和文本
</div>
//更好的 mask,需要调整大小,就先这样,上面的mask径变角落不是半径,所以才出现的百分比不对问题
mask: radial-gradient(circle farthest-side at center,transparent, transparent 80%, #000 80%, #000 100%);
这样就实现了我们的进度条百分比
下面是再进一步改进后,加入背景和中间百分比的,是不是更好看了呢,这算是我们的最终效果了
对于背景的框,可以使用我们的 border + 图片解决就行,absolute 还记得怎么使用么
我们稍微封装一下,再加上一个动画
const ProgressView = (props: {
progress: number;
className?: string;
timeInterval?: number;
}) => {
const [progress, setProgress] = useState<number>(0);
const intervalRef = useRef<number>();
useEffect(() => {
updateProgress(props.progress);
return () => {
if (intervalRef.current) {
cancelAnimationFrame(intervalRef.current);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props]);
const updateProgress = (newProgress: number) => {
if (typeof newProgress !== "number") return;
if (intervalRef.current) {
cancelAnimationFrame(intervalRef.current);
}
const prg = newProgress ?? 0;
const timeInterval = props.timeInterval ?? 900;
const interval = (prg / timeInterval) * 16;
let next = progress;
const callback = () => {
const newNext = next + interval;
if (newNext > newProgress) {
next = newProgress;
} else {
next = newNext;
}
setProgress(next);
if (next >= newProgress && intervalRef.current) {
cancelAnimationFrame(intervalRef.current);
} else {
requestAnimationFrame(callback);
}
};
intervalRef.current = requestAnimationFrame(callback);
};
return (
<div
className={`size-[240px] relative flex justify-center items-center ${
props.className ?? ""
}`}
>
<div
className="absolute margin-auto size-[196px]"
style={{
borderRadius: "50%",
background: `conic-gradient( #00C0FF 0%, #475FFF 50%, #8F00FF 100%)`,
mask: "radial-gradient(transparent, transparent 58%, #000 58%, #000 100%)",
}}
/>
<div
className="absolute margin-auto size-[196px]"
style={{
borderRadius: "50%",
background: `conic-gradient(transparent 0%, transparent ${progress}%, #3C4F69 ${progress}%, #3C4F69 100%)`,
mask: "radial-gradient(transparent 58%, #000 58%)",
}}
/>
<div className="flex justify-center items-center absolute top-0 left-0 right-0 bottom-0">
<img
alt=""
className="2-[141px] h-[139px] absolute"
src={CircleCenterImg}
/>
<p className="absolute margin-auto text-center text-[32px] text-hexi-gradient">
{props.progress}%
</p>
</div>
</div>
);
};
//更好的 mask,需要调整大小,就先这样,上面的mask径变角落不是半径,所以才出现的百分比不对问题
mask: radial-gradient(circle farthest-side at center,transparent, transparent 80%, #000 80%, #000 100%);