React/TypeScript全屏网站:超长幻灯片滚动交互方案问询
解决方案思路与实现代码
核心逻辑
- 接管全局滚动事件,替代scroll snap或第三方库的默认行为,精准控制滚动逻辑
- 实时判断每个幻灯片是否可滚动(内容高度超过100vh)
- 滚动时优先处理当前幻灯片的内部滚动:未滚到底/顶时正常滚动,触达边界后切换幻灯片
- 监听内容高度变化(如手风琴展开),动态更新幻灯片的可滚动状态
React/TypeScript 实现代码
1. 幻灯片组件定义
import { useEffect, useRef, useState } from 'react'; type SlideProps = { children: React.ReactNode; }; const Slide = ({ children }: SlideProps, ref: React.Ref<HTMLSectionElement>) => { return <section ref={ref} className="slide">{children}</section>; }; const ForwardedSlide = React.forwardRef(Slide);
2. 全屏滑块主组件
const FullPageSlider = () => { const containerRef = useRef<HTMLDivElement>(null); const slidesRef = useRef<(HTMLSectionElement | null)[]>([]); const [currentIndex, setCurrentIndex] = useState(0); const [scrollableSlides, setScrollableSlides] = useState<boolean[]>([]); // 更新幻灯片可滚动状态 const updateScrollableStatus = () => { const statusList = slidesRef.current.map(slide => { if (!slide) return false; return slide.scrollHeight > window.innerHeight; }); setScrollableSlides(statusList); }; // 处理滚动事件 useEffect(() => { const handleWheel = (e: WheelEvent) => { e.preventDefault(); const currentSlide = slidesRef.current[currentIndex]; if (!currentSlide) return; const isScrollable = scrollableSlides[currentIndex]; const atBottom = currentSlide.scrollTop + window.innerHeight >= currentSlide.scrollHeight - 5; const atTop = currentSlide.scrollTop <= 5; // 向下滚动逻辑 if (e.deltaY > 0) { if (isScrollable && !atBottom) { currentSlide.scrollTop += e.deltaY; } else if (currentIndex < slidesRef.current.length - 1) { setCurrentIndex(prev => prev + 1); slidesRef.current[currentIndex + 1]?.scrollTo(0, 0); } } // 向上滚动逻辑 else if (e.deltaY < 0) { if (isScrollable && !atTop) { currentSlide.scrollTop += e.deltaY; } else if (currentIndex > 0) { setCurrentIndex(prev => prev - 1); slidesRef.current[currentIndex - 1]?.scrollTo(0, 0); } } }; // 监听内容高度变化(手风琴展开/收起) const resizeObserver = new ResizeObserver(updateScrollableStatus); slidesRef.current.forEach(slide => slide && resizeObserver.observe(slide)); containerRef.current?.addEventListener('wheel', handleWheel, { passive: false }); updateScrollableStatus(); return () => { containerRef.current?.removeEventListener('wheel', handleWheel); resizeObserver.disconnect(); }; }, [currentIndex, scrollableSlides]); // 幻灯片切换时的平滑滚动 useEffect(() => { const targetSlide = slidesRef.current[currentIndex]; if (targetSlide && containerRef.current) { containerRef.current.scrollTo({ top: targetSlide.offsetTop, behavior: 'smooth' }); } }, [currentIndex]); return ( <div ref={containerRef} className="fullpage-container"> <ForwardedSlide ref={el => slidesRef.current[0] = el}> <h1>第一页(全屏)</h1> <p>滚动鼠标尝试切换到下一页</p> </ForwardedSlide> <ForwardedSlide ref={el => slidesRef.current[1] = el}> <h2>第二页(可滚动)</h2> <div className="accordion"> <button onClick={() => { const content = document.querySelector('.accordion-content'); content?.classList.toggle('expanded'); }}>点击展开更多内容</button> <div className="accordion-content"> {Array.from({ length: 30 }).map((_, i) => ( <p key={i}>展开内容第{i+1}行</p> ))} </div> </div> <p>滚动到底部后继续滚动,将切换到第三页</p> </ForwardedSlide> <ForwardedSlide ref={el => slidesRef.current[2] = el}> <h1>第三页(全屏)</h1> <p>向上滚动可回到第二页</p> </ForwardedSlide> </div> ); };
3. 配套CSS样式
.fullpage-container { height: 100vh; overflow-y: auto; scroll-behavior: smooth; } .slide { min-height: 100vh; width: 100%; padding: 2rem; box-sizing: border-box; } .accordion-content { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; } .accordion-content.expanded { max-height: 1000px; }
关键细节说明
- ResizeObserver:实时监测幻灯片高度变化,解决手风琴展开后动态变为可滚动的问题
- 滚动边界容错:设置5px误差范围,避免因滚动精度问题导致的切换异常
- 事件拦截:通过
preventDefault接管滚动行为,避免浏览器默认滚动与自定义逻辑冲突 - 平滑切换:结合CSS的
scroll-behavior与JS的scrollTo方法,保证幻灯片切换的流畅性
扩展优化方向
- 添加触摸滑动支持:监听
touchstart/touchmove事件,计算触摸位移模拟滚动逻辑 - 键盘导航:支持上下箭头键切换幻灯片,逻辑与滚动事件一致
- 动态幻灯片:若需动态添加/删除幻灯片,需在操作后重新调用
updateScrollableStatus更新状态
内容的提问来源于stack exchange,提问作者eto3542
相关产品推荐
相关产品推荐

