欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 焦点 > React实现无缝滚动轮播图

React实现无缝滚动轮播图

2025/3/30 0:38:38 来源:https://blog.csdn.net/qq_59625204/article/details/145930663  浏览:    关键词:React实现无缝滚动轮播图

实现效果:

 由于是演示代码,我是直接写在了App.tsx里面在

文件位置如下:

App.tsx代码如下:

import { useState, useEffect, useCallback, useRef } from "react";
import { ImageContainer } from "./view/ImageContainer";// 图片列表配置
const IMAGE_LIST = ["https://oss.cloudhubei.com.cn/cms/release/set35/20241014/9ec7a083198223c49c06e3ebbf8df33c.jpg","https://img0.baidu.com/it/u=3231399477,1564831636&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","https://q4.itc.cn/images01/20240810/879397b2e3ed4bb8b35be2f272d26b7a.jpeg","https://img1.baidu.com/it/u=3484599935,468270965&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800",
];// 常量配置
const TRANSITION_DURATION = 500; // 过渡动画持续时间
const AUTO_PLAY_INTERVAL = 2000; // 自动播放间隔时间
const USER_INTERACTION_DELAY = 1000; // 用户交互后暂停自动播放的时间function App() {// 状态管理const [imgIndex, setImgIndex] = useState<number>(0); // 当前显示的图片索引const [isAutoPlaying, setIsAutoPlaying] = useState(false); // 是否自动播放const [isSliding, setIsSliding] = useState(false); // 是否正在滑动const [direction, setDirection] = useState<"left" | "right">("left"); // 滑动方向const [isPaused, setIsPaused] = useState(false); // 是否暂停(鼠标悬停时)const [translateX, setTranslateX] = useState(0); // 滑动距离const [userInteracting, setUserInteracting] = useState(false); // 用户是否正在交互const userInteractingTimer = useRef<NodeJS.Timeout | null>(null); // 用户交互定时器// 处理图片过渡效果const handleSlideTransition = useCallback((nextIndex: number, slideDirection: "left" | "right") => {if (isSliding) return;setDirection(slideDirection);setIsSliding(true);// 计算每张图片的宽度百分比const itemWidth = 100 / (IMAGE_LIST.length + 1);if (nextIndex >= IMAGE_LIST.length) {// 处理从最后一张到第一张的无缝滚动setTranslateX(-(nextIndex * itemWidth));setTimeout(() => {setIsSliding(false);setTranslateX(0);setImgIndex(0);}, TRANSITION_DURATION);} else {// 普通图片切换setTranslateX(-(nextIndex * itemWidth));setTimeout(() => {setImgIndex(nextIndex);setIsSliding(false);}, TRANSITION_DURATION);}},[isSliding]);// 处理用户交互状态const handleUserInteraction = useCallback(() => {setUserInteracting(true);if (userInteractingTimer.current) {clearTimeout(userInteractingTimer.current);}userInteractingTimer.current = setTimeout(() => {setUserInteracting(false);}, USER_INTERACTION_DELAY);}, []);// 下一张图片const handleNext = useCallback(() => {if (isSliding) return;handleUserInteraction();const nextIndex =imgIndex === IMAGE_LIST.length - 1 ? IMAGE_LIST.length : imgIndex + 1;handleSlideTransition(nextIndex, "left");}, [imgIndex, isSliding, handleSlideTransition, handleUserInteraction]);// 上一张图片const handlePrevious = useCallback(() => {if (isSliding) return;handleUserInteraction();const previousIndex = imgIndex === 0 ? IMAGE_LIST.length - 1 : imgIndex - 1;handleSlideTransition(previousIndex, "right");}, [imgIndex, isSliding, handleSlideTransition, handleUserInteraction]);// 点击指示器切换图片const handleDotClick = useCallback((index: number) => {if (isSliding || index === imgIndex) return;handleUserInteraction();// 处理特殊情况的无缝滚动if (imgIndex === IMAGE_LIST.length - 1 && index === 0) {handleSlideTransition(IMAGE_LIST.length, "left");} else if (imgIndex === 0 && index === IMAGE_LIST.length - 1) {handleSlideTransition(index, "right");} else {const slideDirection = index > imgIndex ? "left" : "right";handleSlideTransition(index, slideDirection);}},[imgIndex, isSliding, handleSlideTransition, handleUserInteraction]);// 鼠标悬停处理const handleMouseEnter = useCallback(() => {if (isAutoPlaying) {setIsPaused(true);}}, [isAutoPlaying]);const handleMouseLeave = useCallback(() => {if (isAutoPlaying) {setIsPaused(false);}}, [isAutoPlaying]);// 切换自动播放状态const handleToggleAutoPlay = useCallback(() => {setIsAutoPlaying((prev) => {if (!prev) {setUserInteracting(false);if (userInteractingTimer.current) {clearTimeout(userInteractingTimer.current);}}return !prev;});}, []);// 初始化useEffect(() => {setTranslateX(0);setImgIndex(0);}, []);// 清理定时器useEffect(() => {return () => {if (userInteractingTimer.current) {clearTimeout(userInteractingTimer.current);}};}, []);// 自动播放控制useEffect(() => {let timer: NodeJS.Timeout;if (isAutoPlaying && !isSliding && !isPaused && !userInteracting) {timer = setInterval(() => {const nextIndex =imgIndex === IMAGE_LIST.length - 1 ? IMAGE_LIST.length : imgIndex + 1;handleSlideTransition(nextIndex, "left");}, AUTO_PLAY_INTERVAL);}return () => {if (timer) {clearInterval(timer);}};}, [isAutoPlaying,isSliding,isPaused,userInteracting,imgIndex,handleSlideTransition,]);return (<div className={`App ${isSliding ? "sliding" : ""}`}><ImageContainercurrentIndex={imgIndex}onNext={handleNext}onPrevious={handlePrevious}onToggleAutoPlay={handleToggleAutoPlay}isAutoPlaying={isAutoPlaying}totalImages={IMAGE_LIST.length}onDotClick={handleDotClick}imgList={IMAGE_LIST}direction={direction}onMouseEnter={handleMouseEnter}onMouseLeave={handleMouseLeave}isSliding={isSliding}translateX={translateX}/></div>);
}export default App;

ImageContainer.tsx代码如下:

import "./ImageContainer.css";// Props 类型定义
interface PropsType {currentIndex: number; // 当前显示的图片索引totalImages: number; // 图片总数imgList: string[]; // 图片列表isSliding: boolean; // 是否正在滑动isAutoPlaying: boolean; // 是否自动播放中direction: "left" | "right"; // 滑动方向translateX: number; // 滑动距离onNext: () => void; // 下一张回调onPrevious: () => void; // 上一张回调onToggleAutoPlay: () => void; // 切换自动播放回调onDotClick: (index: number) => void; // 点击指示器回调onMouseEnter: () => void; // 鼠标进入回调onMouseLeave: () => void; // 鼠标离开回调
}export const ImageContainer = (props: PropsType) => {// 创建包含额外图片的数组(在末尾添加第一张图片的副本,用于无缝滚动)const extendedImages = [...props.imgList, props.imgList[0]];// 计算滑动容器样式const sliderStyle = {transform: `translateX(${props.translateX}%)`,width: `${(props.totalImages + 1) * 100}%`, // 总宽度包含额外的图片};// 构建滑动容器的类名const sliderClassName = ["image-slider",props.direction,props.isSliding ? "sliding" : "",].filter(Boolean).join(" ");// 渲染控制按钮组const renderControls = () => (<div className="button-group"><button className="control-btn" onClick={props.onPrevious}>上一张</button><button className="control-btn" onClick={props.onToggleAutoPlay}>{props.isAutoPlaying ? "停止" : "自动播放"}</button><button className="control-btn" onClick={props.onNext}>下一张</button></div>);// 渲染指示器小圆点const renderDots = () => (<div className="dots-container">{Array.from({ length: props.totalImages }).map((_, index) => (<divkey={index}className={`dot ${index === props.currentIndex ? "active" : ""}`}onClick={() => props.onDotClick(index)}/>))}</div>);return (<divclassName="image-container"onMouseEnter={props.onMouseEnter}onMouseLeave={props.onMouseLeave}>{/* 图片滑动容器 */}<div className={sliderClassName} style={sliderStyle}>{extendedImages.map((img, index) => (<imgkey={index}className="fullscreen-img"src={img}alt={`slide-${index}`}style={{ width: `${100 / (props.totalImages + 1)}%` }}/>))}</div>{/* 控制器容器 */}<div className="controls-container">{renderControls()}{renderDots()}</div></div>);
};

ImageContainer.css代码如下:

/* 容器样式 */
.image-container {position: relative;width: 100vw;height: 100vh;overflow: hidden;background-color: #000;
}/* 滑动容器样式 */
.image-slider {position: relative;height: 100%;display: flex;
}/* 滑动过渡效果 */
.image-slider.sliding {transition: transform 0.5s ease-out;
}/* 图片样式 */
.fullscreen-img {height: 100%;object-fit: cover;flex-shrink: 0;
}/* 控制器容器样式 */
.controls-container {position: fixed;bottom: 40px;left: 0;right: 0;display: flex;flex-direction: column;align-items: center;gap: 20px;z-index: 10;
}/* 按钮组样式 */
.button-group {display: flex;gap: 20px;
}.control-btn {padding: 10px 20px;border: none;border-radius: 20px;background-color: rgba(0, 0, 0, 0.5);color: white;cursor: pointer;transition: background-color 0.3s;font-size: 14px;
}.control-btn:hover {background-color: rgba(0, 0, 0, 0.8);
}/* 指示器样式 */
.dots-container {display: flex;gap: 12px;
}.dot {width: 12px;height: 12px;border-radius: 50%;background-color: rgba(255, 255, 255, 0.5);cursor: pointer;transition: all 0.3s;
}.dot:hover:not(.active) {background-color: rgba(255, 255, 255, 0.7);transform: scale(1.1);
}.dot.active {background-color: white;transform: scale(1.1);
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词