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

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

相关产品推荐
方舟 Agent Plan

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

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