html+js+css实现一个圆形滑块,可以拖动,可以点击,先看效果再讲原理,最后附上源码:
产品经理设计了这样一个需求,通过拖动圆形滑块实现时间的设置功能,虽然看着有点复杂,但是确实有点复杂。
实现思路
需求分析:
-
一个圆盘,一个滑块;
-
以圆盘为圆心,点击圆盘任意位置或者拖动滑块,滑块会移动到指定位置;
-
同步保存滑块的值
实现逻辑:
获取鼠标点击或者移动的位置到圆盘圆心水平x和垂直y的距离;
然后使用Math.atan2函数根据x和y计算出这个点到圆心这条线和x轴的角度angle,angle的范围为 -π 到 π;
当点击的位置位于x轴上方时,angle小于零,位于x轴下方时,angle大于零
最后根据这个angle,就可以获取一个相对于这个圆形360°的百分比,配合设置的最大值和最小值计算出当前滑块的值。
代码实现
这里就不说太多废话了,直接把完整代码放下面,注释很清楚😉
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#container{
width: 200px;
height: 200px;
border-radius: 100px;
background: aqua;
margin: 50px;
}
#bar{
width: 20px;
height: 20px;
background: red;
border-radius: 10px;
pointer-events: none;
text-align: center;
font-size: 12px;
line-height: 20px;
color: white;
}
</style>
</head>
<body>
<div id="container">
<!-- <div class="track"></div>-->
<div id="bar"></div>
</div>
<script>
// 滑块容器
const slider = document.getElementById('container')
// 滑块
const bar = document.getElementById('bar')
// 滑动轨道半径
const radius = 100
// 滑块的值,以及最小和最大
let value = 50, min = 0, max = 100
// 当前是否正在拖动
let isDragging = false
// 鼠标按下事件
slider.addEventListener('mousedown', (event) => {
event.preventDefault();
// 开始拖动
isDragging = true;
// 监听鼠标移动事件
slider.addEventListener('mousemove', handleMouseMove);
// 鼠标松开事件
slider.addEventListener('mouseup', endDrag);
// 鼠标移出区域
slider.addEventListener('mouseleave', endDrag);
})
// 处理鼠标移动事件
const handleMouseMove = (event) => {
if (isDragging) {
updateValue(event)
}
}
// 更新滑块数值
const updateValue = event => {
// 获取容器中心点
const centerX = slider.offsetWidth / 2;
const centerY = slider.offsetHeight / 2;
// 鼠标位置到中心x和y方向的距离
const deltaX = event.offsetX - centerX
const deltaY = event.offsetY - centerY
// 根据距离计算鼠标到中心点的角度,Math.atan2返回值为 -π 到 π ,导致滑动的位置时从圆形的最右端开始滑动的,所以用 + Math.PI/2处理一些初始位置
let angle = Math.atan2(deltaY, deltaX) + Math.PI/2
// 转换为角度 0-360
let newValue = ((angle * 180) / Math.PI + 360) % 360;
// 将360分段
newValue = Math.round(newValue/3.6)*3.6
// 根据百分比计算滑块的值
newValue = (newValue / 360) * (max - min) + min;
if (newValue <= min) {
newValue = max;
} else if (newValue > max) {
newValue = max;
}
value = newValue.toFixed(0)
bar.innerText = value
// 更新滑块位置
updatePosition()
}
// 鼠标点击时也更新滑块的值
slider.addEventListener('click', updateValue);
// 更新滑块位置
const updatePosition = () => {
// 根据值计算角度,然后计算出滑块的位置
let angle = ((value - min) / (max - min)) * 360 - 90;
const x = Math.cos((angle * Math.PI) / 180) * radius + 90;
const y = Math.sin((angle * Math.PI) / 180) * radius + 90;
bar.style.transform = `translate(${x}px, ${y}px)`
}
// 根据默认值更新滑块位置
bar.innerText = value
updatePosition()
// 结束拖动
const endDrag = () => {
isDragging = false;
// 移除相关监听事件
slider.removeEventListener('mousemove', handleMouseMove);
slider.removeEventListener('mouseup', endDrag);
slider.removeEventListener('mouseleave', endDrag);
}
</script>
</body>
</html>
大家有任何问题都可以在评论区留言交流,相互学习!