如何创建带有3个状态的可拖拽切换开关?
分段滑块(Segmented Slider)实现方案
组件名称说明
你要做的这个组件叫做分段滑块(Segmented Slider),也可称为锚点吸附式切换滑块,这类组件常用于多选项的可视化切换场景,搜索时用这个关键词就能找到相关教程。
完整实现代码
HTML(新增文本显示容器)
<div class="offer-con"> <div class="switch-text">选项1</div> <div class="switch-bar"> <div class="switch-slider"></div> <div class="dot"></div> <div class="dot"></div> <div class="dot"></div> </div> </div>
SCSS(调整定位逻辑,修复溢出问题)
$secondary: #286A75; $linear: linear-gradient(180deg, rgba(0,159,227,1) 0%, rgba(40,57,117,1) 100%); .offer-con { display: flex; flex-direction: column; align-items: center; gap: 8px; } .switch-text { font-size: 18px; color: #333; font-weight: 500; } .switch-bar { position: relative; /* 改为相对定位,作为滑块的父容器 */ height: 45px; width: 90%; max-width: 1000px; border-radius: 80px; margin: 16px; background: $secondary; box-shadow: inset 4px 4px 8px 4px rgba(#000000, 0.25); justify-content: space-between; box-sizing: border-box; align-items: center; padding: 0 24px; /* 调整内边距,避免圆点贴边 */ } .switch-slider { position: absolute; height: 45px; width: 25%; max-width: 300px; border-radius: 138px; background: $linear; cursor: grab; box-shadow: 8px 8px 12px 4px rgba(#000000, 0.4); left: 0; /* 初始位置 */ top: 0; transition: left 0.2s ease; /* 添加平滑过渡 */ } .switch-slider:active { cursor: grabbing; } .dot { background-color: #ededed; height: 16px; width: 16px; border-radius: 50%; z-index: 1; /* 确保圆点在滑块上方 */ }
JavaScript(实现拖拽、吸附与文本切换)
const slider = document.querySelector('.switch-slider'); const switchBar = document.querySelector('.switch-bar'); const switchText = document.querySelector('.switch-text'); const dots = document.querySelectorAll('.dot'); let isDragging = false; let startX; let sliderLeft; const optionTexts = ['选项1', '选项2', '选项3']; let currentIndex = 0; // 计算锚点位置(滑块中心对齐圆点) function getAnchorPositions() { const barWidth = switchBar.offsetWidth; const sliderWidth = slider.offsetWidth; // 三个圆点的位置是bar的1/4、2/4、3/4处,减去滑块宽度的一半让中心对齐 return [ (barWidth * 0.25) - (sliderWidth / 2), (barWidth * 0.5) - (sliderWidth / 2), (barWidth * 0.75) - (sliderWidth / 2) ]; } let anchors = getAnchorPositions(); // 窗口大小变化时重新计算锚点 window.addEventListener('resize', () => { anchors = getAnchorPositions(); // 重置滑块到当前索引的锚点 slider.style.left = `${anchors[currentIndex]}px`; }); // 拖拽开始 slider.addEventListener('mousedown', (e) => { isDragging = true; startX = e.clientX; sliderLeft = parseInt(window.getComputedStyle(slider).left); slider.style.transition = 'none'; // 拖拽时关闭过渡 }); // 拖拽过程 document.addEventListener('mousemove', (e) => { if (!isDragging) return; const barWidth = switchBar.offsetWidth; const sliderWidth = slider.offsetWidth; // 计算新的left值,限制在0到(barWidth - sliderWidth)之间 const newLeft = Math.max(0, Math.min(sliderLeft + (e.clientX - startX), barWidth - sliderWidth)); slider.style.left = `${newLeft}px`; }); // 拖拽结束 document.addEventListener('mouseup', () => { if (!isDragging) return; isDragging = false; slider.style.transition = 'left 0.2s ease'; // 恢复过渡 // 找到最近的锚点 const currentLeft = parseInt(window.getComputedStyle(slider).left); const closestIndex = anchors.reduce((prev, curr, index) => { return Math.abs(curr - currentLeft) < Math.abs(anchors[prev] - currentLeft) ? index : prev; }, 0); // 吸附到锚点并更新文本 if (closestIndex !== currentIndex) { currentIndex = closestIndex; slider.style.left = `${anchors[currentIndex]}px`; updateText(); } }); // 点击圆点直接切换 dots.forEach((dot, index) => { dot.addEventListener('click', () => { currentIndex = index; slider.style.left = `${anchors[currentIndex]}px`; updateText(); }); }); // 更新上方文本 function updateText() { switchText.textContent = optionTexts[currentIndex]; // 这里可以添加你需要执行的自定义函数 // 例如:customFunction(currentIndex); } // 初始化位置 slider.style.left = `${anchors[0]}px`;
关键逻辑说明
- 锚点计算:基于开关条宽度和滑块宽度,计算三个锚点的位置,确保滑块中心与圆点对齐
- 范围限制:拖拽时通过
Math.max和Math.min限制滑块的移动范围,避免超出开关条 - 吸附逻辑:拖拽结束时计算当前位置与三个锚点的距离,自动吸附到最近的锚点
- 响应式适配:窗口大小变化时重新计算锚点位置,保证滑块始终对齐圆点
- 额外交互:添加了点击圆点直接切换的功能,提升用户体验
内容的提问来源于stack exchange,提问作者Moritz Müller
相关产品推荐
相关产品推荐

