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);
}
相关文章:

React实现无缝滚动轮播图
实现效果: 由于是演示代码,我是直接写在了App.tsx里面在 文件位置如下: App.tsx代码如下: import { useState, useEffect, useCallback, useRef } from "react"; import { ImageContainer } from "./view/ImageC…...

deepseek+mermaid【自动生成流程图】
成果: 第一步打开deepseek官网(或百度版(更快一点)): 百度AI搜索 - 办公学习一站解决 第二步,生成对应的Mermaid流程图: 丢给deepseek代码,或题目要求 生成mermaid代码 第三步将代码复制到me…...

分布式锁的简单实现
1. 什么是分布式锁? 在分布式系统中,也会涉及到多个节点访问同一个公共资源的情况,和 Java 中多线程的锁不一样,由于分布式系统中涉及到多个进程多个主机,所以说 Java 中 synchronized 就不合适了。 2. 分布式锁的简…...

C语言(19)----------->函数(2)
本文介绍了C语言的return语句及其它在C语言函数中的作用,以及介绍了二维数组和一维数组传参时的一些注意事项和使用数组传参时的方法。 若没有学习过C语言的一维数组和二维数组,建议参考如下文章: C语言(15)--------…...

动态扩缩容引发的JVM堆内存震荡:从原理到实践的GC调优指南
目录 一、典型案例:系统发布后的GC雪崩事件 (一)故障现象 1. 刚刚启动时 GC 次数较多 2. 堆内存锯齿状波动 3. GC日志特征:Allocation Failure (二)问题定位 二、原理深度解析:JVM内存弹…...
为何在用户注销时使用 location.href 而非 Vue Router 的 router.push
在开发 Web 应用时,用户注销功能的设计看似简单,但背后隐藏着对状态管理、安全性和用户体验的深层考量。以下将详细探讨为何许多项目在注销跳转时选择 location.href(强制刷新页面)而非 Vue Router 的 router.push(单页…...

开源工具推荐:Uptime Kuma监控
1. 概述 Github:louislam/uptime-kuma: A fancy self-hosted monitoring tool Uptime Kuma is an easy-to-use self-hosted monitoring tool. Uptime Kuma 是一款开源的监控工具,可以帮助你实时监测网站或服务的状态,并在发生故障时及时通…...

《基于Selenium的论坛系统自动化测试实战报告》
一、项目背景与技术选型 项目简介 目标系统:论坛系统 核心功能:用户注册/登录、会话框发送信息、好友列表、信息发送 技术栈:html Springboot MySQL数据库 为什么选择Selenium 支持多浏览器兼容性测试(Chrome/Firefox/Edge&…...
深入解析SQL Server高级SQL技巧
SQL Server 是一种功能强大的关系型数据库管理系统,广泛应用于各种数据驱动的应用程序中。在开发过程中,掌握一些高级SQL技巧,不仅能提高查询性能,还能优化开发效率。这篇文章将全面深入地探讨SQL Server中的一些高级技巧…...
分布式中间件:环境准备
在当今数字化的时代,分布式系统已经成为了开发领域的主流。分布式中间件在其中扮演着至关重要的角色,它能够帮助我们更好地处理高并发、高可用等复杂的业务场景。在这个系列的博客中,我将带大家深入学习分布式中间件的相关知识,主…...

c# winform程序 vs2022 打包生成安装包
最近,利用c# winform程序该客户开发一套进销存管理系统,项目在部署前,需要生成安装包,以便部署在客户电脑上面。总结步骤如下: 1、在打包之前 (VS中需要包括Microsoft visual studio installer projects扩展项目)&…...

探索Elasticsearch:文档的CRUD
在企业环境中,Elasticsearch对文档操作的支持不仅是实现高效搜索的关键,更是数据驱动决策的重要支柱。它通过强大的索引机制和灵活的查询语言,使企业能够实时处理和分析海量文档数据,迅速获取有价值的洞察,从而加速创新…...
面试基础--Spring Boot启动流程及源码实现
深度解析Spring Boot启动流程及源码实现 一、Spring Boot启动全景图(含核心阶段) #mermaid-svg-dYTQ6WPa3o6vKFHh {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-dYTQ6WPa3o6vKFHh .error-i…...

火语言RPA--PDF提取图片
【组件功能】:提取PDF文档指定位置图片 配置预览 配置说明 文件路径 支持T或# 默认FLOW输入项 待提取图片的PDF文件的完整路径。 提取位置 全部、指定页、指定范围3种位置供选择。 PDF文件密码 支持T或# 打开PDF文件的密码。 页码 支持T或# 提取指定页的页…...
力扣977.有序数组的平方(双指针)
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。 方法一:直接将每个元素的平方压入ans数组中,再对ans数组进行排序 class Solution { public:vector<int> sort…...

QT——文件IO
QFile 类 构造函数 QFile() 无参构造 仅仅构建一个QFile 对象,不设定文件名 QFile(文件名) 构建一个QFile对象的同时,设定文件名 但是注意,仅仅设定文件名,并不会打开该文件 设定文件名 QFile file file.setFileName…...
分布式中间件:Redis介绍
目录 Redis 概述 Redis 的特点 高性能 丰富的数据结构 持久化 分布式特性 简单易用 Redis 的数据结构 字符串(String) 哈希(Hash) 列表(List) 集合(Set) 有序集合&…...
服务器和本地电脑之间如何传输文件
在服务器和本地电脑之间传输文件可以通过多种方式实现,常见的方法包括使用 SFTP(安全文件传输协议)、SCP(安全复制协议)、FTP(文件传输协议)、rsync、以及 云存储 等工具。以下是几种常见的方法…...

经验分享:用一张表解决并发冲突!数据库事务锁的核心实现逻辑
背景 对于一些内部使用的管理系统来说,可能没有引入Redis,又想基于现有的基础设施处理并发问题,而数据库是每个应用都避不开的基础设施之一,因此分享个我曾经维护过的一个系统中,使用数据库表来实现事务锁的方式。 之…...

嵌入式学习前要了解的基础知识
一、电压和电流 在嵌入式开发中,电压和电流是两个基本的电气概念,对于理解和设计电子电路至关重要。它们直接影响到嵌入式系统的性能、功耗、可靠性和安全性。 电压(Voltage) 电压是电场力推动电荷移动的能力,通常以…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...

Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...

Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...