css环形渐变(CSS锥形渐变实现环形进度条)
10月份因为疫情原因 、又开启了居家办公模式 ,空闲之余 ,与其选择“躺平 ” ,不如去做一些有意义的事情 ,内心的想法驱使着我去做些什么 ,但是又没有合适的素材 ,直到接手了最近的一个可视化项目 ,一个图表勾起了我无限的好奇心 ,本着对技术死磕到底的想法 ,于是开启了我的探索之旅 。具体的原型效果如下:
关于此类进度条的实现方式 ,在我之前的章节(SVG绘制圆环进度条)中也有涉及 ,本章则另辟蹊跷 ,从另一个维度简单介绍一下CSS锥形渐变(conic-gradient)在可视化图表中的应用场景 。本章依旧采用vue+原生css的形式进行案例展示 、在了解本章节之前,需要对vue框架 、css变量 、css属性conic-gradient有一定程度的认识 。案例实现效果如下:
实现思路:首先从原型图入手 ,我们可以将效果图进行拆分 ,背景圆环+进度圆环+进度条开始处小圆点(和边框一样大小 、模拟圆角效果)+进度尾部圆点+进度尾部小眼睛+进度条中心内容 。因此我们只需要将以上几个小功能点实现即可
1.背景圆环:div添加背景颜色+圆角
2.进度圆环:使用css属性conic-gradient进行进度控制
3.进度条开始处小圆点:使用伪元素(::before或::after)或div均可,定位解决
4.进度条尾部圆点:相当于在一个指针上添加一个小球 ,然后将指针根据数值旋转一定的角度
5.进度尾部小眼睛:使用指针头部小球元素的伪元素进行定位
6.进度条中心内容:可根据需要 ,使用插槽的形式解决
首先看一下前两个图表的具体实现细节:
<!-- demo1.vue --> <template> <div class="chart-box" :style="styObj"> <!-- 进度条部分 --> <div class="outer-box"> <div class="inner-box"> <div class="pointer-box"></div> </div> </div> <!-- 插槽内容 --> <div class="slot-content"> <slot></slot> </div> </div> </template> <script> export default { props: { rate: { type: Number, default: 0, }, config: { type: Object, default: () => { return {}; }, }, }, computed: { styObj() { let rate = 0; if (this.rate <= 0) { rate = 0; } else if (this.rate >= 1) { rate = 1; } else { rate = this.rate; } let endPos = `${rate * 100}%`; let obj = Object.assign({}, this.defaultConfig, this.config); let rotate = `rotate(${360 * rate}deg)`; let chartRotate = obj.clockwise ? "rotateY(0deg)" : "rotateY(180deg)"; let showEyes = obj.showEyes ? 1 : 0; return { "--background-image": `conic-gradient(${obj.startColor} 0%, ${obj.endColor} ${endPos}, transparent ${endPos})`, "--border-width": obj.borderWidth, "--dot-width": obj.circleSize, "--pointer-rotate": rotate, "--background-color": obj.borderBackground, "--center-gap-bg": obj.centerCircleBg, "--circle-color": obj.circleColor, "--clockwise-wise": chartRotate, "--show-eyes": showEyes, "--eyes-size": obj.eyesSize, "--start-color": obj.startColor, }; }, }, data() { return { /* 此配置下所有属性均可在config中进行覆盖 ,实现个性化配置 */ defaultConfig: { borderWidth: "8px", // 描边宽度 borderBackground: "#eee", // 描边背景颜色 circleSize: "16px", // 结尾处圆点直径 circleColor: "#2ec4a7", // 结尾处圆点颜色 startColor: "#d5f4ee", // 进度条起始颜色 endColor: "#2ec4a7", // 进度条结束颜色 centerCircleBg: "#fff", // 中间空心圆背景 clockwise: true, // 是否顺时针 showEyes: false, // 是否显示结尾处小眼睛 eyesSize: "8px", // 结尾处小眼睛大小 }, }; }, }; </script> <style scoped> .chart-box { position: relative; width: 100%; height: 100%; } /* 核心代码 、控制进度条样式及进度 */ .outer-box { width: 100%; height: 100%; border-radius: 50%; box-sizing: border-box; background-color: var(--background-color); background-image: var(--background-image); padding: var(--border-width); transform: var(--clockwise-wise); } /* 开始处增加一个圆形端点, 模拟圆角效果 */ .outer-box::after { content: ""; width: var(--border-width); height: var(--border-width); border-radius: 50%; position: absolute; left: 50%; top: 0; transform: translateX(-50%); background: var(--start-color); } /* 中间添加一个和背景色一样的圆圈 */ .inner-box { position: relative; width: 100%; height: 100%; border-radius: 50%; background: var(--center-gap-bg); } /* 指示针 */ .pointer-box { position: absolute; left: 50%; top: calc(0px - var(--border-width) / 2); bottom: calc(0px - var(--border-width) / 2); z-index: 1; transform-origin: center center; transform: var(--pointer-rotate); } /* 指示针的头部添加一个小圆点 */ .pointer-box::after { content: ""; position: absolute; left: 50%; top: 0; width: var(--dot-width); height: var(--dot-width); border-radius: 50%; background: var(--circle-color); transform: translate(-50%, -50%); } /* 进度条结尾处添加一个小眼睛 ,背景白色 */ .pointer-box::before { content: ""; position: absolute; left: 50%; top: 0; width: var(--eyes-size); height: var(--eyes-size); border-radius: 50%; background: #fff; transform: translate(-50%, -50%); z-index: 1; opacity: var(--show-eyes); } /* 插槽内容样式 */ .slot-content { position: absolute; left: 0; top: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; } </style>分析:从代码中不难看出 、进度圆环中间的空白地方(class类名为“inner-box ”)使用了一个背景为白色的元素进行遮盖 ,这点需要根据具体场景进行微调 ,在纯色背景下并无大碍 ,但是在有背景图的场景下 ,显示效果就有点差强人意了 。因此此处需要做一下优化 ,扒一扒css手册 ,刚好有一个属性可以解决这个问题 ,那就是mask属性了 ,优化后代码如下,实现效果见第三 、四个图表
<!-- demo2 --> <template> <div class="chart-box" :style="styObj"> <!-- 进度条部分 --> <div class="process-wrapper"> <div class="process-box"></div> <div class="pointer-box"></div> </div> <!-- 插槽内容 --> <div class="slot-content"> <slot></slot> </div> </div> </template> <script> export default { props: { rate: { type: Number, default: 0, }, config: { type: Object, default: () => { return {}; }, }, }, computed: { styObj() { let rate = 0; if (this.rate <= 0) { rate = 0; } else if (this.rate >= 1) { rate = 1; } else { rate = this.rate; } let endPos = `${rate * 100}%`; let obj = Object.assign({}, this.defaultConfig, this.config); let rotate = `rotate(${360 * rate}deg)`; let chartRotate = obj.clockwise ? "rotateY(0deg)" : "rotateY(180deg)"; let showEyes = obj.showEyes ? 1 : 0; return { "--background-image": `conic-gradient(${obj.startColor} 0%, ${obj.endColor} ${endPos}, transparent ${endPos})`, "--border-width": obj.borderWidth, "--dot-width": obj.circleSize, "--pointer-rotate": rotate, "--background-color": obj.borderBackground, "--circle-color": obj.circleColor, "--clockwise-wise": chartRotate, "--show-eyes": showEyes, "--eyes-size": obj.eyesSize, "--start-color": obj.startColor, }; }, }, data() { return { /* 此配置下所有属性均可在config中进行覆盖 ,实现个性化配置 */ defaultConfig: { borderWidth: "8px", // 描边宽度 borderBackground: "#eee", // 描边背景颜色 circleSize: "16px", // 结尾处圆点直径 circleColor: "#2ec4a7", // 结尾处圆点颜色 startColor: "#d5f4ee", // 进度条起始颜色 endColor: "#2ec4a7", // 进度条结束颜色 clockwise: true, // 是否顺时针 showEyes: false, // 是否显示结尾处小眼睛 eyesSize: "8px", // 结尾处小眼睛大小 }, }; }, }; </script> <style scoped> .chart-box { position: relative; width: 100%; height: 100%; } /* 将图表和插槽内容分开 ,便于控制进度条顺时针亦或是逆时针 */ .process-wrapper { position: relative; width: 100%; height: 100%; transform: var(--clockwise-wise); } /* 开始处增加一个圆形端点,模拟圆角效果 */ .process-wrapper::after { content: ""; width: var(--border-width); height: var(--border-width); border-radius: 50%; position: absolute; left: 50%; top: 0; transform: translateX(-50%); background: var(--start-color); } /* 核心代码 、控制进度条样式及进度 */ .process-box { position: absolute; left: 0; top: 0; width: 100%; height: 100%; border-radius: 50%; box-sizing: border-box; background-color: var(--background-color); background-image: var(--background-image); -webkit-mask: radial-gradient( closest-side at center center, transparent calc(100% - var(--border-width)), #fff calc(100% - var(--border-width)) ); mask: radial-gradient( closest-side at center center, transparent calc(100% - var(--border-width)), #fff calc(100% - var(--border-width)) ); } /* 指示针 */ .pointer-box { position: absolute; left: 50%; top: calc(0px + var(--border-width) / 2); bottom: calc(0px + var(--border-width) / 2); z-index: 1; transform: var(--pointer-rotate); } /* 指示针的头部(进度条结尾处)添加一个小圆点 */ .pointer-box::after { content: ""; position: absolute; left: 50%; top: 0; width: var(--dot-width); height: var(--dot-width); border-radius: 50%; background: var(--circle-color); transform: translate(-50%, -50%); } /* 进度条结尾处添加一个小眼睛 ,背景白色 */ .pointer-box::before { content: ""; position: absolute; left: 50%; top: 0; width: var(--eyes-size); height: var(--eyes-size); border-radius: 50%; background: #fff; transform: translate(-50%, -50%); z-index: 1; opacity: var(--show-eyes); } /* 插槽内容样式 */ .slot-content { position: absolute; left: 0; top: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; } </style>至此 、已完成了进度条的优化改造 、不过还存在一个小小的瑕疵 、使用mask后 ,进度条内部交合区域稍微也有一点锯齿感 ,这个暂时还没有找到优化措施 ,不过并无大碍 。
为了实现更加丰富的展现形式、我们可以在进度条上添加分割线实现花纹效果 、这个其实也不麻烦 、只需要再添加一层锥形渐变即可解决 ,具体实现如下:
<!-- demo3 --> <template> <div class="chart-box" :style="styObj"> <div class="process-box"> <div class="center-mask"></div> </div> <!-- 插槽内容 --> <div class="slot-content"> <slot></slot> </div> </div> </template> <script> export default { props: { rate: { type: Number, default: 0, }, config: { type: Object, default: () => { return {}; }, }, }, computed: { styObj() { let rate = 0; if (this.rate <= 0) { rate = 0; } else if (this.rate >= 1) { rate = 1; } else { rate = this.rate; } let endPos = `${rate * 100}%`; let obj = Object.assign({}, this.defaultConfig, this.config); let rotate = `rotate(${360 * rate}deg)`; let chartRotate = obj.clockwise ? "rotateY(0deg)" : "rotateY(180deg)"; let bgInfo = []; let gap = 100 / obj.gapNum; for (let i = 0; i < obj.gapNum; i++) { bgInfo.push(`#fff ${i * gap}%`); bgInfo.push(`#fff ${i * gap + obj.lineWidth}%`); bgInfo.push(`transparent ${i * gap + obj.lineWidth}%`); bgInfo.push(`transparent ${(i + 1) * gap}%`); } return { "--background-image": `conic-gradient(${bgInfo.join(",")})`, "--background-image1": `conic-gradient(${obj.startColor} 0%, ${obj.endColor} ${endPos}, transparent ${endPos})`, "--border-width": obj.borderWidth, "--background-color": obj.borderBackground, "--center-gap-bg": obj.centerCircleBg, "--clockwise-wise": chartRotate, }; }, }, data() { return { /* 此配置下所有属性均可在config中进行覆盖 ,实现个性化配置 */ defaultConfig: { borderWidth: "8px", // 描边宽度 borderBackground: "#eee", // 描边背景颜色 startColor: "#d5f4ee", // 进度条起始颜色 endColor: "#2ec4a7", // 进度条结束颜色 centerCircleBg: "#fff", // 中间空心圆背景 clockwise: true, // 是否顺时针 gapNum: 10, // 分割段数 lineWidth: 2, // 间隔线宽度 ,百分比 }, }; }, }; </script> <style scoped> .chart-box { position: relative; width: 100%; height: 100%; } .process-box { position: relative; width: 100%; height: 100%; border-radius: 50%; padding: var(--border-width); box-sizing: border-box; background-color: var(--background-color); background-image: var(--background-image), var(--background-image1); transform: var(--clockwise-wise); } .center-mask { width: 100%; height: 100%; border-radius: 50%; background: var(--center-gap-bg); } /* 插槽内容样式 */ .slot-content { position: absolute; left: 0; top: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; } </style>最后 ,在汇总页面中依次将三个组件引入 ,增加不同的个性化参数 ,即可实现封面的展示效果 ,贴一下汇总页面代码
<template> <div class="page-box"> <div class="main-box"> <!-- 第一种实现方式 ,中间的镂空部分采用背景色(和页面背景一致)的形式 --> <div class="module"> <conic-gradient :rate="0.8888"> <span class="slot-font1">88.88%</span> </conic-gradient> </div> <div class="module"> <conic-gradient :rate="0.8888" :config="config"> <div class="slot-bg"> <span class="slot-font2">88.88%</span> </div> </conic-gradient> </div> <!-- 第二种实现方式,中间镂空部分采用遮罩(mask)的方式实现 --> <div class="module"> <conic-mask :rate="0.6666" :config="{ showEyes: true, eyesSize: 6px, circleSize: 8px }" > <span class="slot-font1">66.66%</span> </conic-mask> </div> <div class="module"> <conic-mask :rate="0.6666" :config="config"> <div class="slot-bg"> <span class="slot-font2">66.66%</span> </div> </conic-mask> </div> </div> <hr /> <!-- 锥形渐变实现花纹进度条 --> <div class="main-box"> <div class="module"> <conic-process :rate="0.6666"> <span class="slot-font1">66.66%</span> </conic-process> </div> <div class="module"> <conic-process :rate="0.8888" :config="{ startColor: #e45739, endColor: #e45739, borderBackground: #fbedea, gapNum: 20, lineWidth: 1, clockwise: false }" > <div class="slot-bg"> <span class="slot-font2">88.88%</span> </div> </conic-process> </div> <div class="module"> <conic-process :rate="0.8888" :config="{ gapNum: 1, lineWidth: 0 }"> <span class="slot-font1">88.88%</span> </conic-process> </div> <div class="module"> <conic-process :rate="0.8888" :config="{ startColor: #e45739, endColor: #e45739, borderBackground: #fbedea, gapNum: 1, lineWidth: 0, clockwise: false, }" > <div class="slot-bg"> <span class="slot-font2">88.88%</span> </div> </conic-process> </div> </div> </div> </template> <script> import ConicGradient from "./demo1"; import ConicMask from "./demo2"; import ConicProcess from "./demo3"; export default { components: { ConicGradient, ConicMask, ConicProcess, }, data() { return { config: { borderWidth: "8px", circleSize: "16px", circleColor: "#e45739", borderColor: "#d5f4ee", startColor: "#eead99", endColor: "#e45739", borderBackground: "#fbedea", centerCircleBg: "#fff", clockwise: false, showEyes: true, }, }; }, }; </script> <style scoped> .page-box { width: 100%; height: 100%; overflow: auto; } .main-box { display: flex; flex-wrap: wrap; width: 100%; } .module { width: 200px; height: 200px; box-sizing: border-box; padding: 20px; } .slot-bg { display: flex; align-items: center; justify-content: center; width: 75%; height: 75%; border-radius: 50%; background: #fbedea; } .slot-font1 { color: #009d84; font-size: 20px; font-weight: bold; } .slot-font2 { color: #e45638; font-size: 20px; font-weight: bold; } </style>好了 ,本章节的内容就到这里 ,如小伙伴有疑问,可评论区留言、随时交流 。
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!