React+TS编写轮播图
当前轮播图存在部分问题,一次循环结束,进入下一次需要点击两次(所以动画效果上点击第二次才出现)
轮播图:实现无限循环轮播图的关键在于"视觉欺骗"——我们在实际数据的前后各添加部分数据副本,当滚动到边缘时快速而无缝地跳转到另一端,从而创造出无限循环的视觉效果。
理想效果
轮播图理想效果:呈现多个元素盒子,并且首元素默认初次呈现居中效果,通过左右按钮控制轮播图的滑动,滑动一次下一个元素又再次居中,居中元素有一个居中动画效果
静态效果:
逻辑思路
初始准备
- 首先在scss里面将track盒子居中,最外层盒子设置overflow:hidden,注意:这里尽量不要去使用transform去让track居中,不然后续ts去控制盒子移动这个效果会被抵消
- 当前居中的元素并非首元素,是track盒子里中的中间元素,给track中间数据的midOder标记为为0因为这是实际的中间元素
- 想要首元素居中,在初始化的时候需要进行一次transform去移动使首元素居中
控制移动
- 计算滑动距离=两个盒子的left距离,这里采用clienWidth因为在我自己项目中是响应式布局,使用clienWidth更适合
- 然后给左右移动按钮添加点击事件,去更新currentIndex
- 普通移动:添加移动效果动画,并且移动currentIdex*sliWidth(transform的效果是覆盖的,会覆盖初始化的首元素为中点,所以track真实的位置初始是真实中间元素是起点也就是midOder为0的元素是起点,所以是相对于这个起点进行的移动)
无限循环效果
- 正常普通移动到第一个克隆dom,然后下一次点击transition为none,视觉上用户看不到已经移动到了下一次循环中
- 然后调用setCurentIndex去更新当前中间元素标记
- 与前面普通移动放在一起,形成判断条件,到达了边缘判断,就进行视觉欺骗
添加中间动画
- 判断当前中间位置currentIndex是否等于元素的midOrder,是的话添加中间效果动画
数据结构准备
const carouselData = [{title: '00',midOrder: -2,},{title: '11',midOrder: -1,},{title: '22',midOrder: 0,},{title: '33',midOrder: 1,},{title: '44',midOrder: 2,},
];
组件实现步骤
1. 扩展数据创建"视觉循环"
const slides = [carouselData[carouselData.length - 3],carouselData[carouselData.length - 2],carouselData[carouselData.length - 1],...carouselData,carouselData[0],carouselData[1],carouselData[2],
];
在原始数据前后各添加3个数据项,这样当滚动到"边缘"时,可以无缝跳转。
2. 状态管理
const [currentIndex, setCurrentIndex] = useState(-2);
const [slideWidth, setSlideWidth] = useState(0);
const trackRef = useRef<HTMLDivElement>(null);
-
currentIndex
表示当前中心项的索引 -
slideWidth
存储每个轮播项的宽度 -
trackRef
用于操作轮播轨道DOM元素
3. 计算轮播项宽度
useEffect(() => {const items = trackRef.current?.querySelectorAll('.activities-recent-item');if (!items || items.length < 2) return;
const first = items[0].getBoundingClientRect();const second = items[1].getBoundingClientRect();setSlideWidth(first.left - second.left);
}, []);
这个效果通过比较相邻两个轮播项的位置来计算单个项的宽度。
4. 处理导航点击
const handleDirection = (dir: 'left' | 'right') => {if (dir === 'left') {setCurrentIndex((prev) => prev - 1);} else {setCurrentIndex((prev) => prev + 1);}
};
点击左右按钮时,更新当前索引。
5. 无限循环修正
useEffect(() => {const track = trackRef.current;if (!track) return;
if (currentIndex === -4) {track.style.transition = 'none';track.style.transform = `translateX(${2 * slideWidth}px)`;setCurrentIndex(2);} else if (currentIndex === 4) {track.style.transition = 'none';track.style.transform = `translateX(${-2 * slideWidth}px)`;setCurrentIndex(-2);} else {track.style.transition = 'transform 0.4s ease';track.style.transform = `translateX(${currentIndex * slideWidth}px)`;}
}, [currentIndex]);
当滚动到边缘时:
-
禁用过渡动画
-
立即跳转到另一端
-
重新启用动画
6. 渲染组件
return (<><div className="rencent-to-left recent-to-button" onClick={() => handleDirection('left')}><i className="iconfont icon-zuojiantou"></i></div>
<div className="activities-recent-container"><div ref={trackRef} className="activities-recent-box track-box">{slides.map((item) => (<div className={`activities-recent-item ${item.midOrder === currentIndex ? 'mid-active' : ''}`}>{item.title}</div>))}</div></div>
<div className="rencent-to-right recent-to-button" onClick={() => handleDirection('right')}><i className="iconfont icon-youjiantou"></i></div></>
);
关键点总结
-
数据扩展:通过在原始数据前后添加副本,实现视觉上的无限循环
-
无缝跳转:当滚动到边缘时,禁用动画并立即跳转
-
精确计算:动态计算轮播项宽度确保滚动精度
-
状态管理:使用React状态控制当前显示项
-
CSS过渡:通过CSS实现平滑的滚动效果