如何用css3实现一个渐变圆环图

前言

大家好,我是辉夜真是太可爱啦,最近有一个需求,是实现渐变圆环饼图,效果如图

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>
相关推荐
玩电脑的辣条哥2 小时前
Python如何播放本地音乐并在web页面播放
开发语言·前端·python
ew452182 小时前
ElementUI表格表头自定义添加checkbox,点击选中样式不生效
前端·javascript·elementui
suibian52352 小时前
AI时代:前端开发的职业发展路径拓宽
前端·人工智能
Moon.92 小时前
el-table的hasChildren不生效?子级没数据还显示箭头号?树形数据无法展开和收缩
前端·vue.js·html
垚垚 Securify 前沿站2 小时前
深入了解 AppScan 工具的使用:筑牢 Web 应用安全防线
运维·前端·网络·安全·web安全·系统安全
工业甲酰苯胺5 小时前
Vue3 基础概念与环境搭建
前端·javascript·vue.js
mosquito_lover16 小时前
怎么把pyqt界面做的像web一样漂亮
前端·python·pyqt
柴柴的小记8 小时前
前端vue引入特殊字体不生效
前端·javascript·vue.js
柠檬豆腐脑9 小时前
从前端到全栈:新闻管理系统及多个应用端展示
前端·全栈
bin91539 小时前
DeepSeek 助力 Vue 开发:打造丝滑的颜色选择器(Color Picker)
前端·javascript·vue.js·ecmascript·deepseek