0
点赞
收藏
分享

微信扫一扫

如何用JavaScript实现一个交互式的圆形进度条

实现一个交互式的圆形进度条,包含动画效果和用户交互功能。

完整实现代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>交互式圆形进度条</title>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            margin: 0;
            padding: 20px;
        }

        .progress-container {
            text-align: center;
            position: relative;
        }

        .progress-circle {
            width: 200px;
            height: 200px;
            position: relative;
        }

        .progress-bg {
            fill: none;
            stroke: rgba(255, 255, 255, 0.2);
            stroke-width: 12;
        }

        .progress-fill {
            fill: none;
            stroke: #ffffff;
            stroke-width: 12;
            stroke-linecap: round;
            transition: stroke-dasharray 0.3s ease;
        }

        .progress-text {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: white;
            font-size: 24px;
            font-weight: bold;
            text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
        }

        .controls {
            margin-top: 30px;
            display: flex;
            gap: 10px;
            justify-content: center;
            flex-wrap: wrap;
        }

        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 25px;
            background: rgba(255, 255, 255, 0.2);
            color: white;
            cursor: pointer;
            font-size: 14px;
            transition: all 0.3s ease;
            backdrop-filter: blur(5px);
        }

        .btn:hover {
            background: rgba(255, 255, 255, 0.3);
            transform: translateY(-2px);
        }

        .btn:active {
            transform: translateY(0);
        }

        .range-control {
            margin-top: 20px;
            width: 80%;
            max-width: 300px;
        }

        .range-control input {
            width: 100%;
            height: 6px;
            -webkit-appearance: none;
            background: rgba(255, 255, 255, 0.3);
            border-radius: 3px;
            outline: none;
        }

        .range-control input::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: white;
            cursor: pointer;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
        }

        .range-control input::-moz-range-thumb {
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: white;
            cursor: pointer;
            border: none;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
        }

        .pulse-animation {
            animation: pulse 2s infinite;
        }

        @keyframes pulse {
            0% { transform: scale(1); }
            50% { transform: scale(1.05); }
            100% { transform: scale(1); }
        }

        .success {
            stroke: #4CAF50 !important;
            animation: success-pulse 0.5s ease-in-out;
        }

        @keyframes success-pulse {
            0% { stroke-width: 12; }
            50% { stroke-width: 16; }
            100% { stroke-width: 12; }
        }

        .warning {
            stroke: #FFC107 !important;
        }

        .danger {
            stroke: #F44336 !important;
        }
    </style>
</head>
<body>
    <div class="progress-container">
        <div class="progress-circle">
            <svg width="200" height="200" viewBox="0 0 200 200">
                <circle class="progress-bg" cx="100" cy="100" r="80" />
                <circle class="progress-fill" cx="100" cy="100" r="80" />
            </svg>
            <div class="progress-text">0%</div>
        </div>
        
        <div class="range-control">
            <input type="range" min="0" max="100" value="0" step="1">
        </div>
        
        <div class="controls">
            <button class="btn" id="btn-25">25%</button>
            <button class="btn" id="btn-50">50%</button>
            <button class="btn" id="btn-75">75%</button>
            <button class="btn" id="btn-100">100%</button>
            <button class="btn" id="btn-reset">重置</button>
            <button class="btn" id="btn-animate">动画演示</button>
        </div>
    </div>

    <script>
        class CircularProgressBar {
            constructor() {
                this.progress = 0;
                this.maxProgress = 100;
                this.radius = 80;
                this.circumference = 2 * Math.PI * this.radius;
                this.animationId = null;
                
                this.initElements();
                this.initEvents();
                this.updateProgress(0);
            }

            initElements() {
                this.svg = document.querySelector('svg');
                this.progressFill = document.querySelector('.progress-fill');
                this.progressText = document.querySelector('.progress-text');
                this.rangeInput = document.querySelector('.range-control input');
                this.buttons = {
                    '25': document.getElementById('btn-25'),
                    '50': document.getElementById('btn-50'),
                    '75': document.getElementById('btn-75'),
                    '100': document.getElementById('btn-100'),
                    'reset': document.getElementById('btn-reset'),
                    'animate': document.getElementById('btn-animate')
                };
            }

            initEvents() {
                // 滑块事件
                this.rangeInput.addEventListener('input', (e) => {
                    this.updateProgress(parseInt(e.target.value));
                });

                // 按钮事件
                this.buttons['25'].addEventListener('click', () => this.setPercentage(25));
                this.buttons['50'].addEventListener('click', () => this.setPercentage(50));
                this.buttons['75'].addEventListener('click', () => this.setPercentage(75));
                this.buttons['100'].addEventListener('click', () => this.setPercentage(100));
                this.buttons['reset'].addEventListener('click', () => this.reset());
                this.buttons['animate'].addEventListener('click', () => this.animateDemo());

                // 点击圆圈事件
                this.svg.addEventListener('click', (e) => {
                    this.handleCircleClick(e);
                });
            }

            updateProgress(percentage) {
                if (percentage < 0) percentage = 0;
                if (percentage > 100) percentage = 100;
                
                this.progress = percentage;
                this.rangeInput.value = percentage;
                this.progressText.textContent = `${Math.round(percentage)}%`;
                
                // 计算stroke-dasharray
                const dashArray = (percentage / 100) * this.circumference;
                this.progressFill.style.strokeDasharray = `${dashArray} ${this.circumference}`;
                
                // 旋转圆圈以从顶部开始
                this.progressFill.style.transform = 'rotate(-90deg)';
                this.progressFill.style.transformOrigin = 'center';
                
                // 根据进度改变颜色
                this.updateProgressColor();
                
                // 触发自定义事件
                this.dispatchEvent('progressChange', { percentage });
            }

            setPercentage(percentage) {
                this.updateProgress(percentage);
            }

            reset() {
                this.animateTo(0);
            }

            animateTo(targetPercentage, duration = 1000) {
                // 取消之前的动画
                if (this.animationId) {
                    cancelAnimationFrame(this.animationId);
                }

                const startPercentage = this.progress;
                const startTime = performance.now();
                const change = targetPercentage - startPercentage;

                const animate = (currentTime) => {
                    const elapsed = currentTime - startTime;
                    const progress = Math.min(elapsed / duration, 1);
                    
                    // 使用缓动函数
                    const easeProgress = this.easeOutCubic(progress);
                    const currentPercentage = startPercentage + (change * easeProgress);
                    
                    this.updateProgress(currentPercentage);
                    
                    if (progress < 1) {
                        this.animationId = requestAnimationFrame(animate);
                    } else {
                        // 动画完成后的效果
                        if (targetPercentage === 100) {
                            this.showSuccessAnimation();
                        }
                        this.animationId = null;
                    }
                };

                this.animationId = requestAnimationFrame(animate);
            }

            easeOutCubic(t) {
                return 1 - Math.pow(1 - t, 3);
            }

            showSuccessAnimation() {
                this.progressFill.classList.add('success');
                setTimeout(() => {
                    this.progressFill.classList.remove('success');
                }, 1000);
            }

            animateDemo() {
                // 重置并演示完整动画
                this.reset();
                setTimeout(() => {
                    this.animateTo(100, 3000);
                }, 500);
            }

            handleCircleClick(e) {
                // 获取点击位置相对于SVG中心的坐标
                const rect = this.svg.getBoundingClientRect();
                const centerX = rect.left + rect.width / 2;
                const centerY = rect.top + rect.height / 2;
                const clickX = e.clientX - centerX;
                const clickY = e.clientY - centerY;
                
                // 计算角度
                let angle = Math.atan2(clickY, clickX) * (180 / Math.PI);
                if (angle < 0) angle += 360;
                
                // 将角度转换为百分比 (0-360度对应0-100%)
                const percentage = (angle / 360) * 100;
                
                // 动画到目标百分比
                this.animateTo(percentage);
            }

            updateProgressColor() {
                // 根据进度改变颜色
                if (this.progress < 30) {
                    this.progressFill.classList.remove('warning', 'success');
                    this.progressFill.classList.add('danger');
                } else if (this.progress < 70) {
                    this.progressFill.classList.remove('danger', 'success');
                    this.progressFill.classList.add('warning');
                } else {
                    this.progressFill.classList.remove('danger', 'warning');
                    this.progressFill.classList.add('success');
                }
            }

            dispatchEvent(eventName, data) {
                const event = new CustomEvent(eventName, {
                    detail: data,
                    bubbles: true,
                    cancelable: true
                });
                this.progressText.dispatchEvent(event);
            }

            // 获取当前进度
            getProgress() {
                return this.progress;
            }

            // 设置进度(带动画)
            setProgress(percentage, animate = true) {
                if (animate) {
                    this.animateTo(percentage);
                } else {
                    this.updateProgress(percentage);
                }
            }
        }

        // 初始化进度条
        document.addEventListener('DOMContentLoaded', () => {
            const progressBar = new CircularProgressBar();
            
            // 监听进度变化事件
            document.querySelector('.progress-text').addEventListener('progressChange', (e) => {
                console.log('进度变化:', e.detail.percentage + '%');
            });
        });
    </script>
</body>
</html>

主要特性

1. 多种交互方式

  • 滑块控制:通过range input精确控制进度
  • 按钮控制:预设25%、50%、75%、100%等快捷按钮
  • 点击圆圈:点击圆圈的任意位置,根据角度自动计算进度
  • 动画控制:支持平滑动画过渡

2. 视觉效果

  • 渐变背景:美观的渐变背景
  • 颜色变化:根据进度自动改变颜色(红色→黄色→绿色)
  • 成功动画:达到100%时有特殊动画效果
  • 悬停效果:按钮有悬停和点击动画

3. 核心功能

// 主要方法
progressBar.setProgress(75);        // 设置进度
progressBar.animateTo(100, 2000);   // 动画到指定进度
progressBar.reset();                // 重置到0%
progressBar.getProgress();          // 获取当前进度

4. 动画系统

  • 缓动函数:使用easeOutCubic缓动函数使动画更自然
  • requestAnimationFrame:使用高性能的动画帧控制
  • 可取消动画:支持取消正在进行的动画

5. 响应式设计

  • 适配不同屏幕尺寸
  • 移动端友好的触摸控制

使用方法

  1. 直接复制代码到HTML文件中运行
  2. 自定义样式:修改CSS部分调整外观
  3. API调用:通过JavaScript对象方法控制进度条
举报

相关推荐

0 条评论