背景介绍
在日常开发中我们不可避免的要使用svg画图来解决如进图条的问题,最近在开发中遇到了一个仪表盘如下图所示:
看到这张图的时候我的第一想法是使用echarts的仪表盘来解决,但很遗憾echarts的仪表盘貌似并没有让指针从中间开始转动的配置,而且开始和结束的时候两个小点也不好处理,所以我们就得用SVG画图来解决。
开始实现
画好背景与画布
首先我们问ui小姐姐要来一张图片作为背景,然后将画布定位在背景图圆弧四周
<div className={styles.panelContain}>
<div className={styles.svgContainer}>
</div>
</div>
.panelContain {
width: 10.125rem;
height: 5.4375rem;
padding: 0.75rem 1rem 0.3125rem 0.375rem;
background-image: url("@/assets/resource-aggregation/pannel-bg.webp");
background-repeat: no-repeat;
background-size: 100% 100%;
}
.svgContainer {
position: relative; /* 设置相对定位,使内部的绝对定位生效 */
width: 100%; /* 让容器占满父元素的宽度 */
height: 0; /* 设置高度为0,由内容决定 */
padding-bottom: 100%; /* 设置 padding-bottom 为百分比,与宽度相同 */
}
效果如图:
画个圆吧
背景好了,我们也该让圆弧展示出来了,这里我们使用SVG中的circle来实现
<div className={styles.panelContain}>
<div className={styles.svgContainer}>
<svg
width="100%"
height="100%"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet"
className={styles.svgContent}
>
<defs>
<linearGradient id="circleGradient" cx="50%" cy="50%" r="50%">
<stop offset={`0`} stopColor="#28C8FF" />
<stop offset="100%" stopColor="#2758FF" />
</linearGradient>
</defs>
<circle
cx="50%"
cy="50%"
r={4.0625 * fontSize}
stroke="url(#circleGradient)"
strokeWidth="0.5rem"
fill="none"
strokeLinecap="round"
/>
</svg>
</div>
</div>
园变半圆
这里我们需要用到strokeDasharray,strokeDashoffset两个属性,一个表示圆环的长度,一个表示圆环的偏移量,我们我们将circle修改一下
<circle
cx="50%"
cy="50%"
r={4.0625 * fontSize}
stroke="url(#circleGradient)"
strokeWidth="0.5rem"
fill="none"
strokeDasharray={2 * Math.PI * 4.0625 * fontSize}
strokeDashoffset={-2 * Math.PI * 4.0625 * fontSize * 0.5}
strokeLinecap="round"
ref={pathRef}
/>
我们发现变成了半圆
进度不从0开始
以图中百分之七十五至百分之百为例:
- 这里我们需要先算出开始位置到结束位置的长度
- 算出这段长度在整个园中的比例,用0.5减去这个比例在加上0.5,则得到圆环需要偏移的量
- 将
strokeDashoffset后面的0.5替换为第2步得到的偏移量
加上开头结尾小圆点
开头结尾的小圆点,同样使用circle在0的位置画图,然后旋转即可 完整代码如下
const [radio, setRadio] = useState(1);
const [rotate, setRotate] = useState(0);
const [minRotate, setMinRotate] = useState(0);
useEffect(() => {
// 这个值是整数的话则逆时针转,负数顺时针转,
// 设置值,0%-120%为0度到162度,大于120%统一设置为170度
// 模拟数据75%-100%
const baseRadio = 162 / 120;
const minNum = baseRadio * 75;
const maxNum = baseRadio * 100;
setRadio(0.5 + (0.5 - (maxNum - minNum) / 360));
setRotate(180 - maxNum);
setMinRotate(180 - minNum);
}, []);
<div className={styles.panelContain}>
<div className={styles.svgContainer}>
<svg
width="100%"
height="100%"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet"
className={styles.svgContent}
ref={svgRef}
style={{ transform: `rotate(-${rotate}deg)` }}
>
<defs>
<linearGradient id="circleGradient" cx="50%" cy="50%" r="50%">
<stop offset={`${radio * 100}%`} stopColor="#28C8FF" />
<stop offset="100%" stopColor="#2758FF" />
</linearGradient>
</defs>
<circle
cx="50%"
cy="50%"
r={4.0625 * fontSize}
stroke="url(#circleGradient)"
strokeWidth="0.5rem"
fill="none"
strokeDasharray={2 * Math.PI * 4.0625 * fontSize}
strokeDashoffset={-2 * Math.PI * 4.0625 * fontSize * radio}
strokeLinecap="round"
ref={pathRef}
/>
</svg>
<svg
width="100%"
height="100%"
style={{ transform: `rotate(-${minRotate}deg)` }}
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet"
className={styles.svgContent}
ref={svgRef}
>
<circle
cx={`calc(100% - ${(5 * fontSize) / 16}px)`}
cy="50%"
r={0.2 * fontSize}
fill="#fff"
/>
</svg>
<svg
width="100%"
height="100%"
style={{ transform: `rotate(-${rotate}deg)` }}
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet"
className={styles.svgContent}
ref={svgRef}
>
<circle
cx={`calc(100% - ${(5 * fontSize) / 16}px)`}
cy="50%"
r={0.2 * fontSize}
fill="#fff"
/>
</svg>
</div>
</div>
.panelContain {
width: 10.125rem;
height: 5.4375rem;
padding: 0.75rem 1rem 0.3125rem 0.375rem;
background-image: url("@/assets/resource-aggregation/pannel-bg.webp");
background-repeat: no-repeat;
background-size: 100% 100%;
}
.svgContainer {
position: relative; /* 设置相对定位,使内部的绝对定位生效 */
width: 100%; /* 让容器占满父元素的宽度 */
height: 0; /* 设置高度为0,由内容决定 */
padding-bottom: 100%; /* 设置 padding-bottom 为百分比,与宽度相同 */
}
.svgContent {
position: absolute; /* 设置绝对定位,使 SVG 元素填充容器 */
width: 100%; /* 使 SVG 元素宽度占满容器 */
height: 100%; /* 使 SVG 元素高度占满容器 */
}