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

React项目中如何让滑块根据选中项索引移动到对应列表项位置

React列表滑块跟随选中项移动解决方案

核心实现逻辑

  • 给所有列表项绑定ref集合,用于实时获取每个元素的实际位置
  • 用state单独保存滑块的纵向偏移量,不要在渲染时动态计算初始值
  • 监听选中下标的变化,每次选中项更新时,获取当前选中项距离父容器顶部的offsetTop值,同步给滑块偏移量的state
  • 给滑块添加CSS过渡属性,实现平滑移动效果

核心代码示例

JSX部分

import { useState, useRef, useEffect } from 'react';

const listOptions = ['选项1', '选项2', '选项3', '选项4', '选项5'];

export default function App() {
  // 保存当前选中下标
  const [activeIndex, setActiveIndex] = useState(0);
  // 保存滑块纵向偏移量
  const [sliderY, setSliderY] = useState(0);
  // 存储所有列表项的ref
  const itemRefs = useRef([]);

  // 选中项变化时更新滑块位置
  useEffect(() => {
    const currentItem = itemRefs.current[activeIndex];
    if (currentItem) {
      setSliderY(currentItem.offsetTop);
    }
  }, [activeIndex]);

  return (
    <div className="list-wrapper">
      {/* 左侧滑块 */}
      <div 
        className="left-slider"
        style={{ transform: `translateY(${sliderY}px)` }}
      />
      {/* 选项列表 */}
      {listOptions.map((text, idx) => (
        <div
          key={idx}
          ref={el => itemRefs.current[idx] = el}
          className={`list-item ${activeIndex === idx ? 'active' : ''}`}
          onClick={() => setActiveIndex(idx)}
        >
          {text}
        </div>
      ))}
    </div>
  );
}

CSS部分

.list-wrapper {
  position: relative;
  width: 220px;
  padding: 8px 0;
}
.left-slider {
  position: absolute;
  left: 0;
  top: 8px; /* 和父容器的上padding对齐 */
  width: 3px;
  height: 28px;
  background-color: #1890ff;
  border-radius: 2px;
  transition: transform 0.25s cubic-bezier(0.3, 0, 0.7, 1);
}
.list-item {
  height: 28px;
  line-height: 28px;
  padding-left: 12px;
  cursor: pointer;
}
.list-item.active {
  color: #1890ff;
  font-weight: 500;
}

原有问题原因

之前的实现之所以滑块每次都从第一项位置开始移动,是因为没有用独立的state存储滑块的当前偏移量,每次渲染都基于初始位置计算过渡,导致过渡的起点始终是初始的第一项位置。

内容的提问来源于stack exchange,提问作者DSteman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 17:48:03