目录

颠覆常规!3 种方式打造圆环组件,不用 ECharts 也能玩出花

前段时间在掘金看到一篇博文,作者提到其公司不允许使用 ECharts,但项目中需要展示简单的圆环图。

于是她用 Canvas 实现了需求,效果也比较不错。评论区也有不少大佬分享了更轻量、更优秀的方案,比如 CSS、SVG、conic-gradient 等,看完让我受益匪浅。

今天就来简单整理一下这些思路,在不依赖 ECharts 的前提下,如何用多种方式实现圆环组件

纯css实现

使用背景渐变 +内层遮罩是实现圆环最简单的 CSS 方式。

html 复制代码
<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8" />
    <title>纯 CSS 彩色圆环图</title>
    <style>
        .circle {
            width: 200px;
            height: 200px;
            border-radius: 50%;
            background: conic-gradient(#FF6B6B 0%,
                    #FF6B6B 30%,
                    #FFD93D 30%,
                    #FFD93D 65%,
                    #6BCB77 65%,
                    #6BCB77 100%);
            mask: radial-gradient(circle at center, transparent 80px, #000 80px);
            -webkit-mask: radial-gradient(circle at center, transparent 80px, #000 80px);
        }
    </style>
</head>

<body>
    <div class="circle"></div>
</body>

</html>

效果:

这种方案的核心在于:

  • 利用背景渐变(conic-gradient)生成一个圆形多色背景,渐变参数中指定的颜色分段(如:红色、黄色、绿色)按照从 0% 到 100% 顺时针分布到整个圆周上。
  • 默认情况下,conic-gradient 生成的是一个实心圆。通过使用径向渐变遮罩(radial-gradient),我们"挖空"了中心区域,只保留外圈,从而形成圆环效果。

如这些css的使用,我们可以借助AI学习,比如使用Trae的Chat模式进行代码解读。

如果你比较专业,直接去MDN的解释也是极佳的。

相关css使用MDN: conic-gradient radial-gradient

CSS函数 conic-gradient()创建一个由渐变组成的图像,渐变的颜色围绕一个中心点旋转(而不是从中心辐射)进行过渡。锥形渐变的例子包括饼图和色轮conic-gradient() 函数的结果是 数据类型的对象,此对象是一种特殊的 数据类型。

radial-gradient() CSS 函数创建一个图像,该图像由从原点辐射的两种或多种颜色之间的渐进过渡组成,其形状可以是圆形或椭圆形。函数的结果是 数据类型的对象,此对象是一种特殊的类型。

在实际项目中,我们可能需要让它变得可配置,这也非常容易,借助Trae的Builder模式可以直接来修改代码:

html 复制代码
<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8" />
    <title>纯 CSS 彩色圆环图</title>
    <style>
        .circle {
            width: 200px;
            height: 200px;
            border-radius: 50%;
            background: conic-gradient(#FF6B6B 0%,
                    #FF6B6B 30%,
                    #FFD93D 30%,
                    #FFD93D 65%,
                    #6BCB77 65%,
                    #6BCB77 100%);
            mask: radial-gradient(circle at center, transparent 80px, #000 80px);
            -webkit-mask: radial-gradient(circle at center, transparent 80px, #000 80px);
        }
    </style>
</head>

<body>
    <div class="circle"></div>
    <div class="controls">
        <label for="size">尺寸:</label>
        <input type="number" id="size" min="100" max="500" value="200">
        <label for="colors">颜色:</label>
        <input type="text" id="colors" value="#FF6B6B 0%,#FF6B6B 30%,#FFD93D 30%,#FFD93D 65%,#6BCB77 65%,#6BCB77 100%">
        <button onclick="updateCircle()">更新</button>
    </div>
    <script>
        function updateCircle() {
            const size = document.getElementById('size').value;
            const colors = document.getElementById('colors').value;
            const circle = document.querySelector('.circle');
            circle.style.width = size + 'px';
            circle.style.height = size + 'px';
            circle.style.background = 'conic-gradient(' + colors + ')';
        }
    </script>
</body>

</html>

生成的代码,我们直接点击接受,看看最终效果:

svg

SVG 拥有天然的图形绘制能力,可控制度高,非常适合实现高质量的圆环图。

xml 复制代码
<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8" />
</head>

<body>
    <svg width="120" height="120" viewBox="0 0 120 120">
        <circle cx="60" cy="60" r="54" stroke="#e0e0e0" stroke-width="12" fill="none" />
        <circle cx="60" cy="60" r="54" stroke="#4caf50" stroke-width="12" fill="none" stroke-dasharray="339.292"
            stroke-dashoffset="101.7876" stroke-linecap="round" transform="rotate(-90 60 60)" />
        <text x="60" y="65" font-size="20" text-anchor="middle" fill="#333">70%</text>
    </svg>
</body>
</html>

stroke-dasharraystroke-dashoffset 用于控制弧长,弧长 = 2πr ≈ 339.292。70% 进度 = 30% 剩余,所以 dashoffset = 339.292 * (1 - 0.7)

效果:

同样的,如果需要做成可配置的,我们也可以将svg的渲染过程封账在一个函数里,通过参数配置最终效果。

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
</head>

<body>
    <div id="app"></div>
    <script>
        function createColoredRing({ size, strokeWidth, segments }) {
            const radius = (size - strokeWidth) / 2;
            const circumference = 2 * Math.PI * radius;

            let dashOffset = 0;

            const svgNS = "http://www.w3.org/2000/svg";
            const svg = document.createElementNS(svgNS, "svg");
            svg.setAttribute("width", size);
            svg.setAttribute("height", size);
            svg.setAttribute("viewBox", `0 0 ${size} ${size}`);

            segments.forEach(segment => {
                const { color, percentage } = segment;
                const segmentLength = (percentage / 100) * circumference;
                const gap = circumference - segmentLength;

                const circle = document.createElementNS(svgNS, "circle");
                circle.setAttribute("cx", size / 2);
                circle.setAttribute("cy", size / 2);
                circle.setAttribute("r", radius);
                circle.setAttribute("fill", "none");
                circle.setAttribute("stroke", color);
                circle.setAttribute("stroke-width", strokeWidth);
                circle.setAttribute("stroke-dasharray", `${segmentLength} ${gap}`);
                circle.setAttribute("stroke-dashoffset", -dashOffset);

                dashOffset += segmentLength;
                svg.appendChild(circle);
            });

            return svg;
        }

        const ring = createColoredRing({
            size: 160,
            strokeWidth: 10,
            segments: [
                { color: "red", percentage: 10 },
                { color: "blue", percentage: 30 },
                { color: "orange", percentage: 60 },
            ]
        })

        document.getElementById("app").appendChild(ring);

    </script>
</body>

配置效果:

canvas

使用 Canvas 绘制圆环,是灵活度最高的一种方式。不仅可以轻松实现颜色渐变、动画过渡等复杂效果,也非常适合封装成独立组件用于多种业务场景。当然,相比其他方式,Canvas 的使用成本和实现难度也更高一些。不过,在这个 AI 助力开发的时代,这些挑战都不再是问题。

Canvas的实现方式非常多,效果也大同小异,这里,我就使用原作者文章中的代码了:

html 复制代码
<template>
    <canvas ref="canvasDom"></canvas>
</template>

<script lang="ts" setup>
import { ref, computed, onMounted, watchEffect } from 'vue';

// 定义 props 的类型
interface RatioItem {
    ratio: number;
    color: string;
}

const props = defineProps<{
    size?: number; // 画布大小
    storkWidth?: number; // 环的宽度
    ratioList?: RatioItem[]; // 比例列表
}>();

// 默认值
const defaultSize = 200; // 默认画布宽高
const defaultStorkWidth = 4;
const defaultRatioList: RatioItem[] = [{ ratio: 1, color: '#C4C9CF4D' }];

// canvas DOM 和上下文
const canvasDom = ref<HTMLCanvasElement | null>(null);
let ctx: CanvasRenderingContext2D | null = null;

// 动态计算 canvas 的中心点和半径
const size = computed(() => props.size || defaultSize);
const center = computed(() => ({
    x: size.value / 2,
    y: size.value / 2
}));
const radius = computed(() => size.value / 2 - (props.storkWidth || defaultStorkWidth));

// 初始化 canvas
const initCanvas = () => {
    const dom = canvasDom.value;
    if (!dom) return;

    ctx = dom.getContext('2d');
    if (!ctx) return;

    dom.width = size.value;
    dom.height = size.value;

    drawBackgroundCircle();
    drawDataRings();
};

// 绘制背景圆环
const drawBackgroundCircle = () => {
    if (!ctx) return;

    drawCircle({
        ctx,
        x: center.value.x,
        y: center.value.y,
        radius: radius.value,
        lineWidth: props.storkWidth || defaultStorkWidth,
        color: '#C4C9CF4D',
        startAngle: -Math.PI / 2,
        endAngle: Math.PI * 1.5
    });
};

// 绘制数据圆环
const drawDataRings = () => {
    const { ratioList = defaultRatioList } = props;
    if (!ctx) return;

    let startAngle = -Math.PI / 2;
    ratioList.forEach(({ ratio, color }) => {
        const endAngle = startAngle + ratio * Math.PI * 2;

        drawCircle({
            ctx,
            x: center.value.x,
            y: center.value.y,
            radius: radius.value,
            lineWidth: props.storkWidth || defaultStorkWidth,
            color,
            startAngle,
            endAngle
        });

        startAngle = endAngle;
    });
};

// 通用绘制函数
const drawCircle = ({
    ctx,
    x,
    y,
    radius,
    lineWidth,
    color,
    startAngle,
    endAngle
}: {
    ctx: CanvasRenderingContext2D;
    x: number;
    y: number;
    radius: number;
    lineWidth: number;
    color: string;
    startAngle: number;
    endAngle: number;
}) => {
    ctx.beginPath();
    ctx.arc(x, y, radius, startAngle, endAngle);
    ctx.lineWidth = lineWidth;
    ctx.strokeStyle = color;
    ctx.stroke();
    ctx.closePath();
};

// 监听画布大小变化
watchEffect(() => {
    initCanvas();
});

onMounted(() => {
    initCanvas();
});
</script>

<style scoped>
canvas {
    display: block;
    margin: auto;
    border-radius: 50%;
}
</style>

使用

ini 复制代码
<Ring
    :storkWidth="5"
    :size="60"
    :ratioList="[
      { ratio: 0.3, color: '#FF5733' },
    { ratio: 0.6, color: '#33FF57' },
    { ratio: 0.1, color: '#3357FF' }
    ]"
  ></Ring>

最终效果:

总结

总的来说,纯 CSS、SVG、Canvas 三种方式各有优劣:

  • 纯 CSS 实现最简单,适合快速展示静态效果,对交互和控制需求不高时非常实用;
  • SVG 控制精度高、性能稳定,适合实现带有文本、进度动画、图标组合等场景;
  • Canvas 灵活度最高,可以轻松支持动画、渐变、多图层叠加等复杂视觉效果,更适合做成组件或用于复杂图表系统中。

至于具体选择哪种方案,取决于项目的复杂度与开发成本的权衡。当然,在 AI 的辅助下,我们可以更快地探索和实现每一种方式的可能性。如果你有兴趣,不妨动手试试~

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
优弧9 小时前
Trae 如何切换插件市场源
trae
创码小奇客10 小时前
MongoDB 事务:数据世界的守护者联盟全解析
spring boot·mongodb·trae
夕水11 小时前
这个提升效率宝藏级工具一定要收藏使用
前端·javascript·trae
天天扭码13 小时前
Trae 04.22 版本:前端学习者的智能成长助手
前端·trae
海底火旺16 小时前
Trae 入门指南:一个更简单、更现代的 HTTP 请求库
人工智能·axios·trae
anyup16 小时前
借助 Trae 从 0 到 1 实现海外地图渲染(leaflet + OSM)
前端·数据可视化·trae
冰镇生鲜19 小时前
Cursor 前端AI编程 最佳实践指南
前端·mcp·trae
二进制独立开发20 小时前
[Trae 04.22+]适用于Vue开发的智能体提示词
vue.js·trae
用户40993225021220 小时前
FastAPI数据库连接池配置与监控
后端·ai编程·trae
berryyan20 小时前
傻瓜教程安装Trae IDE用AI撰写第一个AKShare接口脚本
python·trae