您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# JS如何实现圆形进度条拖拽滑动
## 引言
在现代Web开发中,交互式UI组件已成为提升用户体验的关键要素。圆形进度条因其视觉直观性和空间效率,被广泛应用于音量控制、文件上传、设置调节等场景。本文将深入探讨如何使用原生JavaScript实现一个**可拖拽滑动**的圆形进度条,涵盖从基础数学原理到完整代码实现的全部过程。
## 一、核心实现原理
### 1.1 圆形几何基础
实现圆形进度条需要掌握以下几何概念:
- **极坐标系转换**:通过`Math.atan2(y, x)`计算点与圆心的角度
- **弧度与角度转换**:`radians = degrees * (Math.PI/180)`
- **圆周点坐标计算**:`x = cx + r * cos(θ)`, `y = cy + r * sin(θ)`
### 1.2 交互逻辑设计
拖拽交互需要处理三个核心事件:
```javascript
const progressCircle = document.getElementById('progress-circle');
progressCircle.addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', endDrag);
<div class="progress-container">
<svg width="200" height="200" viewBox="0 0 200 200">
<!-- 背景圆 -->
<circle class="progress-bg" cx="100" cy="100" r="90" />
<!-- 进度圆 -->
<circle class="progress-fill"
cx="100" cy="100" r="90"
stroke-dasharray="565.48"
stroke-dashoffset="565.48" />
</svg>
<div class="drag-handle"></div>
<span class="progress-text">0%</span>
</div>
.progress-container {
position: relative;
width: 200px;
height: 200px;
}
.progress-fill {
fill: none;
stroke: #4285f4;
stroke-width: 10;
stroke-linecap: round;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
.drag-handle {
position: absolute;
width: 20px;
height: 20px;
background: #fff;
border: 3px solid #4285f4;
border-radius: 50%;
cursor: pointer;
}
const container = document.querySelector('.progress-container');
const fill = document.querySelector('.progress-fill');
const handle = document.querySelector('.drag-handle');
const text = document.querySelector('.progress-text');
const center = { x: 100, y: 100 };
const radius = 90;
const circumference = 2 * Math.PI * radius;
let isDragging = false;
function updateProgress(angle) {
// 限制角度范围0-360
angle = Math.max(0, Math.min(360, angle));
// 计算dashoffset
const offset = circumference - (angle / 360) * circumference;
fill.style.strokeDashoffset = offset;
// 更新手柄位置
const rad = (angle - 90) * (Math.PI / 180);
handle.style.left = `${center.x + radius * Math.cos(rad) - 10}px`;
handle.style.top = `${center.y + radius * Math.sin(rad) - 10}px`;
// 更新文本
text.textContent = `${Math.round(angle / 3.6)}%`;
}
function getAngleFromEvent(e) {
const rect = container.getBoundingClientRect();
const x = e.clientX - rect.left - center.x;
const y = e.clientY - rect.top - center.y;
// 计算角度(0-360)
let angle = Math.atan2(y, x) * (180 / Math.PI) + 90;
return angle < 0 ? angle + 360 : angle;
}
function startDrag(e) {
isDragging = true;
updateProgress(getAngleFromEvent(e));
}
function drag(e) {
if (!isDragging) return;
updateProgress(getAngleFromEvent(e));
}
function endDrag() {
isDragging = false;
}
// 添加触摸事件监听
handle.addEventListener('touchstart', (e) => {
e.preventDefault();
startDrag(e.touches[0]);
});
document.addEventListener('touchmove', (e) => {
if (!isDragging) return;
e.preventDefault();
drag(e.touches[0]);
});
document.addEventListener('touchend', endDrag);
.progress-fill {
transition: stroke-dashoffset 0.3s ease-out;
}
.drag-handle {
transition: left 0.2s, top 0.2s;
}
handle.setAttribute('tabindex', '0');
handle.addEventListener('keydown', (e) => {
const step = e.shiftKey ? 10 : 1;
let angle = parseFloat(fill.style.strokeDashoffset) / circumference * 360;
switch(e.key) {
case 'ArrowUp':
case 'ArrowRight':
angle += step;
break;
case 'ArrowDown':
case 'ArrowLeft':
angle -= step;
break;
default:
return;
}
updateProgress(angle);
e.preventDefault();
});
requestAnimationFrame
节流拖拽更新let lastFrameTime = 0;
function drag(e) {
if (!isDragging || performance.now() - lastFrameTime < 16) return;
lastFrameTime = performance.now();
updateProgress(getAngleFromEvent(e));
}
<!DOCTYPE html>
<html>
<head>
<style>
/* 包含前文CSS代码 */
</style>
</head>
<body>
<!-- 包含前文HTML代码 -->
<script>
// 包含全部JavaScript实现代码
// 初始化默认进度
document.addEventListener('DOMContentLoaded', () => {
updateProgress(0);
});
</script>
</body>
</html>
function updateProgress(newAngle) {
const oldAngle = currentAngle;
// 处理跨越360°的过渡
if (Math.abs(newAngle - oldAngle) > 180) {
fill.style.transition = 'none';
handle.style.transition = 'none';
requestAnimationFrame(() => {
updateProgress(newAngle > 180 ? newAngle - 360 : newAngle + 360);
requestAnimationFrame(() => {
fill.style.transition = '';
handle.style.transition = '';
updateProgress(newAngle);
});
});
return;
}
// ...原有逻辑
}
-webkit-
前缀确保Safari兼容PointerEvent
替代部分鼠标事件if (window.PointerEvent) {
handle.addEventListener('pointerdown', startDrag);
document.addEventListener('pointermove', drag);
document.addEventListener('pointerup', endDrag);
}
通过本文的逐步实现,我们完成了一个功能完善的圆形进度条组件。关键要点包括: 1. 极坐标系与DOM位置的转换 2. SVG路径属性的动态控制 3. 完整的拖拽交互事件链 4. 跨设备的触摸支持
读者可以在此基础上扩展更多功能,如多颜色分段、双向拖拽、数据持久化等。完整项目代码已托管在GitHub仓库供参考。
扩展阅读:
- SVG路径动画高级技巧
- Pointer Events规范 “`
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。