You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建带有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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 15:07:17