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

如何在React JS中实现类似Facebook Stories的轮播(含指定导航按钮效果)

React实现Facebook Stories风格轮播组件(含指定导航按钮效果)

以下是完整的实现方案,包含你需要的悬停显示左右导航按钮、自动播放、进度追踪等核心功能:

核心功能实现

1. 组件结构与状态管理

使用React Hooks管理当前故事索引、进度、导航按钮显示状态及定时器,确保逻辑清晰且性能优化。

2. 完整可运行代码

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

const StoriesCarousel = ({ stories }) => {
  const [currentIndex, setCurrentIndex] = useState(0);
  const [progress, setProgress] = useState(0);
  const [showNavButtons, setShowNavButtons] = useState(false);
  const timerRef = useRef(null);
  const STORY_LENGTH = 5000; // 单故事播放时长(毫秒)

  // 自动播放与进度更新
  useEffect(() => {
    if (!showNavButtons) startAutoPlay();
    return () => clearAutoPlay();
  }, [currentIndex, showNavButtons]);

  const startAutoPlay = () => {
    clearAutoPlay();
    setProgress(0);
    const interval = 50;
    const progressStep = (interval / STORY_LENGTH) * 100;
    
    timerRef.current = setInterval(() => {
      setProgress(prev => {
        if (prev >= 100) {
          clearAutoPlay();
          goToNextStory();
          return 0;
        }
        return prev + progressStep;
      });
    }, interval);
  };

  const clearAutoPlay = () => {
    if (timerRef.current) {
      clearInterval(timerRef.current);
      timerRef.current = null;
    }
  };

  // 故事切换逻辑
  const goToNextStory = () => {
    setCurrentIndex(prev => (prev + 1) % stories.length);
  };

  const goToPrevStory = () => {
    setCurrentIndex(prev => (prev - 1 + stories.length) % stories.length);
  };

  return (
    <div 
      className="stories-container"
      onMouseEnter={() => {
        setShowNavButtons(true);
        clearAutoPlay();
      }}
      onMouseLeave={() => {
        setShowNavButtons(false);
        startAutoPlay();
      }}
      style={{
        position: 'relative',
        width: '420px',
        height: '680px',
        borderRadius: '16px',
        overflow: 'hidden',
        backgroundColor: '#000',
        margin: '0 auto',
      }}
    >
      {/* 故事内容区 */}
      <img
        src={stories[currentIndex].src}
        alt={stories[currentIndex].alt}
        style={{
          width: '100%',
          height: '100%',
          objectFit: 'cover',
        }}
      />

      {/* 顶部进度条组 */}
      <div style={{
        position: 'absolute',
        top: '20px',
        left: '20px',
        right: '20px',
        display: 'flex',
        gap: '8px',
        height: '4px',
      }}>
        {stories.map((_, idx) => (
          <div key={idx} style={{
            flex: 1,
            backgroundColor: 'rgba(255,255,255,0.3)',
            borderRadius: '2px',
            overflow: 'hidden',
          }}>
            <div style={{
              height: '100%',
              backgroundColor: '#ffffff',
              width: idx === currentIndex ? `${progress}%` : idx < currentIndex ? '100%' : '0%',
              transition: 'width 0.05s linear',
            }} />
          </div>
        ))}
      </div>

      {/* 左右导航按钮 */}
      {showNavButtons && (
        <>
          <button
            onClick={goToPrevStory}
            style={{
              position: 'absolute',
              left: '20px',
              top: '50%',
              transform: 'translateY(-50%)',
              width: '52px',
              height: '52px',
              borderRadius: '50%',
              border: 'none',
              backgroundColor: 'rgba(0,0,0,0.7)',
              color: '#ffffff',
              fontSize: '28px',
              cursor: 'pointer',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              zIndex: 10,
            }}
          >
            ←
          </button>
          <button
            onClick={goToNextStory}
            style={{
              position: 'absolute',
              right: '20px',
              top: '50%',
              transform: 'translateY(-50%)',
              width: '52px',
              height: '52px',
              borderRadius: '50%',
              border: 'none',
              backgroundColor: 'rgba(0,0,0,0.7)',
              color: '#ffffff',
              fontSize: '28px',
              cursor: 'pointer',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              zIndex: 10,
            }}
          >
            →
          </button>
        </>
      )}
    </div>
  );
};

// 使用示例
const App = () => {
  const demoStories = [
    { src: '/path/to/your/image1.jpg', alt: '旅行故事' },
    { src: '/path/to/your/image2.jpg', alt: '美食分享' },
    { src: '/path/to/your/image3.jpg', alt: '日常随拍' },
  ];

  return (
    <div style={{ padding: '30px' }}>
      <StoriesCarousel stories={demoStories} />
    </div>
  );
};

export default App;

关键功能说明

  • 导航按钮触发逻辑:鼠标悬停在故事容器上时显示按钮并暂停自动播放,离开时隐藏按钮并恢复播放
  • 进度条同步:顶部的进度条组会实时反映每个故事的播放状态,已播放完成的故事进度条会完全填充
  • 循环轮播:通过取模运算实现故事的无缝循环切换,不会出现索引越界问题
  • 性能优化:使用useRef存储定时器引用,避免组件重渲染时重复创建定时器

扩展方向

  • 支持触摸滑动切换(适配移动端)
  • 添加视频故事的播放控制逻辑
  • 允许点击进度条直接跳转到对应故事
  • 增加加载状态和错误 fallback 处理

内容的提问来源于stack exchange,提问作者Md. Abul Khair

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 09:05:39