前言
大家好,我是辉夜真是太可爱啦,最近有一个需求,是实现渐变圆环饼图,效果如图
conic-gradient
首先,从渐变分析,这里需要使用角向渐变。
html
<div class="circle-chart-component-box">
<div
class="pie-item"
:style="{
background: `conic-gradient(var(--one-color) 0%,transparent 20%)`,
}"
></div>
</div>
.circle-chart-component-box {
position: relative;
--one-color: #ff67a8;
--two-color: #6eec9b;
--three-color: #f97b7b;
--four-color: #4ae4f0;
--five-color: #f4aa6a;
.pie-item {
position: absolute;
width: 144px;
height: 144px;
}
可以看到目前的效果如图
那我们再给他一个 border-radius: 50%;
接下来,我们使用 mask
,将中间的部分隐藏,这样子就能实现一个圆环啦。
css
mask: radial-gradient(
transparent,
transparent 47px,
#000 48px,
#000 48px,
#000 100%
);
接下来,使用 before
伪类,在 pie-item
头部加上一个圆圈。
css
&::before {
content: '';
position: absolute;
inset: 0;
width: 24px;
height: 24px;
top: 0;
left: 60px;
border-radius: 50%;
}
当然,别忘记设置 pie-item
的颜色。
css
&:nth-child(1)::before {
background: linear-gradient(
90deg,
var(--one-color) 50%,
transparent 51%,
transparent 100%
);
}
也别忘记剩余的颜色
css
&:nth-child(2)::before {
background: linear-gradient(
90deg,
var(--two-color) 50%,
transparent 51%,
transparent 100%
);
}
&:nth-child(3)::before {
background: linear-gradient(
90deg,
var(--three-color) 50%,
transparent 51%,
transparent 100%
);
}
&:nth-child(4)::before {
background: linear-gradient(
90deg,
var(--four-color) 50%,
transparent 51%,
transparent 100%
);
}
&:nth-child(5)::before {
background: linear-gradient(
90deg,
var(--five-color) 50%,
transparent 51%,
transparent 100%
);
}
接下来,我们就完成了第一个圆环,接下来就是让其余的元素算旋转的角度即可。
数据对接
假设我们当前的值为 data: [8, 7, 6, 5, 4]
,我们先计算出总数。
javascript
computed: {
total() {
return this.data?.reduce((prev = 0, curr) => prev + curr) || 0
},
},
然后我们计算出每项在圆环中的百分比,值为 circleData
javascript
this.circleData = [
Math.ceil((this.data[0] / this.total) * 100) || 0,
Math.ceil((this.data[1] / this.total) * 100) || 0,
Math.ceil((this.data[2] / this.total) * 100) || 0,
Math.ceil((this.data[3] / this.total) * 100) || 0,
Math.ceil((this.data[4] / this.total) * 100) || 0,
]
然后计算出每个圆环的旋转角度,值为 rotateData
javascript
const num1 = Math.ceil((this.data[0] / this.total) * 100) || 0
const num2 =
num1 + (Math.ceil((this.data[1] / this.total) * 100) || 0)
const num3 = num2 + Math.ceil((this.data[2] / this.total) * 100) || 0
const num4 = num3 + Math.ceil((this.data[3] / this.total) * 100) || 0
this.rotateData = [num1, num2, num3, num4]
最后将 rotateData
以及 circleData
应用在模板中。
html
<div
class="pie-item"
v-if="circleData[0]"
:style="{
background: `conic-gradient(var(--one-color) 0%,transparent ${circleData[0]}%)`,
}"
></div>
<div
class="pie-item"
v-if="circleData[1]"
:style="{
background: `conic-gradient(var(--two-color) 0%,transparent ${circleData[1]}%)`,
transform: `rotate(${(rotateData[0] / 100) * 360}deg)`,
}"
></div>
<div
class="pie-item"
v-if="circleData[2]"
:style="{
background: `conic-gradient(var(--three-color) 0,transparent ${circleData[2]}%)`,
transform: `rotate(${(rotateData[1] / 100) * 360}deg)`,
}"
></div>
<div
class="pie-item"
v-if="circleData[3]"
:style="{
background: `conic-gradient(var(--four-color) 0%,transparent ${circleData[3]}%)`,
transform: `rotate(${(rotateData[2] / 100) * 360}deg)`,
}"
></div>
<div
class="pie-item"
v-if="circleData[4]"
:style="{
background: `conic-gradient(var(--five-color) 0%,transparent ${circleData[4]}%)`,
transform: `rotate(${(rotateData[3] / 100) * 360}deg)`,
}"
></div>
然后就完成了该图表,可能这个数据不太明显,我们将它的数据进行修改为 [20, 7, 6, 5, 4]
组件完整代码
最终,我们将其封装为一个组件,仅需传入 data
即可,格式参考为 [20, 7, 6, 5, 4]
html
<template>
<div class="circle-chart-component-box">
<div
class="pie-item"
v-if="circleData[0]"
:style="{
background: `conic-gradient(var(--one-color) 0%,transparent ${circleData[0]}%)`,
}"
></div>
<div
class="pie-item"
v-if="circleData[1]"
:style="{
background: `conic-gradient(var(--two-color) 0%,transparent ${circleData[1]}%)`,
transform: `rotate(${(rotateData[0] / 100) * 360}deg)`,
}"
></div>
<div
class="pie-item"
v-if="circleData[2]"
:style="{
background: `conic-gradient(var(--three-color) 0,transparent ${circleData[2]}%)`,
transform: `rotate(${(rotateData[1] / 100) * 360}deg)`,
}"
></div>
<div
class="pie-item"
v-if="circleData[3]"
:style="{
background: `conic-gradient(var(--four-color) 0%,transparent ${circleData[3]}%)`,
transform: `rotate(${(rotateData[2] / 100) * 360}deg)`,
}"
></div>
<div
class="pie-item"
v-if="circleData[4]"
:style="{
background: `conic-gradient(var(--five-color) 0%,transparent ${circleData[4]}%)`,
transform: `rotate(${(rotateData[3] / 100) * 360}deg)`,
}"
></div>
</div>
</template>
<script>
export default {
name: 'circleChart',
props: {
data: {
type: Array,
default: () => [],
},
},
data() {
return {
circleData: [0, 0, 0, 0, 0],
rotateData: [0, 0, 0, 0],
}
},
computed: {
total() {
return this.data?.reduce((prev = 0, curr) => prev + curr) || 0
},
},
watch: {
data: {
handler(newValue) {
if (newValue.length > 0) {
this.circleData = [
Math.ceil((newValue[0] / this.total) * 100) || 0,
Math.ceil((newValue[1] / this.total) * 100) || 0,
Math.ceil((newValue[2] / this.total) * 100) || 0,
Math.ceil((newValue[3] / this.total) * 100) || 0,
Math.ceil((newValue[4] / this.total) * 100) || 0,
]
const num1 = Math.ceil((newValue[0] / this.total) * 100) || 0
const num2 = num1 + (Math.ceil((newValue[1] / this.total) * 100) || 0)
const num3 = num2 + (Math.ceil((newValue[2] / this.total) * 100) || 0)
const num4 = num3 + (Math.ceil((newValue[3] / this.total) * 100) || 0)
this.rotateData = [num1, num2, num3, num4]
}
},
deep: true,
immediate: true,
},
},
}
</script>
<style lang="scss" scoped>
.circle-chart-component-box {
position: relative;
--one-color: #ff67a8;
--two-color: #6eec9b;
--three-color: #f97b7b;
--four-color: #4ae4f0;
--five-color: #f4aa6a;
.pie-item {
position: absolute;
width: 144px;
height: 144px;
border-radius: 50%;
mask: radial-gradient(
transparent,
transparent 47px,
#000 48px,
#000 48px,
#000 100%
);
&:nth-child(1)::before {
background: linear-gradient(
90deg,
var(--one-color) 50%,
transparent 51%,
transparent 100%
);
}
&:nth-child(2)::before {
background: linear-gradient(
90deg,
var(--two-color) 50%,
transparent 51%,
transparent 100%
);
}
&:nth-child(3)::before {
background: linear-gradient(
90deg,
var(--three-color) 50%,
transparent 51%,
transparent 100%
);
}
&:nth-child(4)::before {
background: linear-gradient(
90deg,
var(--four-color) 50%,
transparent 51%,
transparent 100%
);
}
&:nth-child(5)::before {
background: linear-gradient(
90deg,
var(--five-color) 50%,
transparent 51%,
transparent 100%
);
}
&::before {
content: '';
position: absolute;
inset: 0;
width: 24px;
height: 24px;
top: 0;
left: 60px;
border-radius: 50%;
}
}
}
</style>