React中轮播图点击切换时出现闪烁问题求助
React+Tailwind轮播切换闪烁/残影问题修复
问题描述
React+Tailwind项目中实现的轮播功能,点击下一页按钮时图片会出现闪烁;仅两张图片切换时,会出现前/后一张图的残影。每次操作都会触发闪烁,但录屏仅能捕捉到第一次的闪烁现象。
现有代码
Index.css
.slide-in-from-right { animation: slideInFromRight 0.5s ease-in; } .slide-out-to-left { animation: slideOutToLeft 0.5s ease-in; } @keyframes slideInFromRight { from { transform: translateX(100%); } to { transform: translateX(0%); } } @keyframes slideOutToLeft { from { transform: translateX(0%); } to { transform: translateX(-100%); } }
HeroSlideshow组件
let count = 0; export default function HeroSlideshow() { const [slide, setSlide] = useState({}); const [clonedSlide, setClonedSlide] = useState({}); const [slides, setSlides] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); const slideRef = useRef(); const clonedSlideRef = useRef(); const { updateNotification } = useNotification(); const fetchLatestUploads = async () => { const { error, movies } = await getLatestUploads(); if (error) return updateNotification("error", error); setSlides([...movies]); setSlide(movies[0]); }; const handleOnNextClick = () => { setClonedSlide(slides[count]); clonedSlideRef.current.classList.add("slide-out-to-left"); count = (count + 1) % slides.length; setSlide(slides[count]); setCurrentIndex(count); slideRef.current.classList.add("slide-in-from-right"); }; const handleAnimationEnd = () => { slideRef.current.classList.remove("slide-in-from-right"); clonedSlideRef.current.classList.remove("slide-out-to-left"); setClonedSlide({}); }; useEffect(() => { fetchLatestUploads(); }, []); return ( <div className="w-full flex"> <div className="w-4/5 aspect-video relative overflow-hidden"> <img ref={slideRef} onAnimationEnd={handleAnimationEnd} className="aspect-video object-cover" src={slide.poster} alt="" /> <img ref={clonedSlideRef} onAnimationEnd={handleAnimationEnd} className="aspect-video object-cover absolute inset-0" src={clonedSlide.poster} alt="" /> <SlideShowController onNextClick={handleOnNextClick} /> </div> <div className="w-1/5 aspect-video bg-red-300"></div> </div> ); } const SlideShowController = ({ onNextClick, onPrevClick }) => { const btnClass = "bg-primary rounded border-2 text-white text-xl p-2 outline-none"; return ( <div className="absolute top-1/2 -translate-y-1/2 w-full flex items-center justify-between px-2"> <button onClick={onPrevClick} className={btnClass} type="button"> <AiOutlineDoubleLeft /> </button> <button onClick={onNextClick} className={btnClass} type="button"> <AiOutlineDoubleRight /> </button> </div> ); };
问题根源
- 全局变量
count未被React追踪:状态更新不同步,导致切换逻辑出现时序错误 - 动画结束时直接清空
clonedSlide:导致DOM中出现src为空的图片,引发闪烁 - 两张图切换时状态逻辑冲突:克隆图与当前图的切换时机不匹配,产生残影
- 动画未等待图片加载完成:图片未加载就执行动画,出现空白闪烁
修复方案
1. 替换全局变量为React状态
将let count = 0改为const [count, setCount] = useState(0),让React追踪状态变化,确保逻辑同步。
2. 优化切换逻辑时序
先保存当前slide作为克隆图,再更新count和当前slide,避免状态混乱。
3. 图片加载完成后触发动画
添加onLoad事件监听,确保图片加载完成后再执行进入动画,避免空白闪烁。
4. 区分动画结束事件
分别处理两个图片元素的动画结束事件,避免互相干扰;动画结束后仅移除类名,不清空克隆图状态,直到下一次切换更新。
5. 条件渲染克隆图
仅当克隆图存在有效poster时才渲染,避免空src图片出现。
6. 优化动画过渡效果
为动画添加透明度变化,让切换更平滑,减少残影感。
修改后的完整代码
优化后的Index.css
.slide-in-from-right { animation: slideInFromRight 0.5s ease-in-out forwards; } .slide-out-to-left { animation: slideOutToLeft 0.5s ease-in-out forwards; } @keyframes slideInFromRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0%); opacity: 1; } } @keyframes slideOutToLeft { from { transform: translateX(0%); opacity: 1; } to { transform: translateX(-100%); opacity: 0; } }
修改后的HeroSlideshow组件
import { useState, useEffect, useRef } from 'react'; import { AiOutlineDoubleLeft, AiOutlineDoubleRight } from 'react-icons/ai'; import { useNotification } from './你的路径'; // 替换为实际路径 import { getLatestUploads } from './你的路径'; // 替换为实际路径 export default function HeroSlideshow() { const [slide, setSlide] = useState({}); const [clonedSlide, setClonedSlide] = useState({}); const [slides, setSlides] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); const [count, setCount] = useState(0); const slideRef = useRef(); const clonedSlideRef = useRef(); const [isSlideLoaded, setIsSlideLoaded] = useState(false); const { updateNotification } = useNotification(); const fetchLatestUploads = async () => { const { error, movies } = await getLatestUploads(); if (error) return updateNotification("error", error); setSlides([...movies]); setSlide(movies[0]); }; const handleOnNextClick = () => { // 保存当前slide作为克隆图 setClonedSlide(slides[count]); // 计算新的索引 const newCount = (count + 1) % slides.length; setCount(newCount); setCurrentIndex(newCount); // 设置新的slide setSlide(slides[newCount]); // 触发克隆图的退出动画 if (clonedSlideRef.current) { clonedSlideRef.current.classList.add("slide-out-to-left"); } // 重置图片加载状态 setIsSlideLoaded(false); }; const handleSlideLoad = () => { setIsSlideLoaded(true); // 图片加载完成后触发进入动画 if (slideRef.current) { slideRef.current.classList.add("slide-in-from-right"); } }; const handleAnimationEnd = (e) => { // 区分两个元素的动画结束事件 if (e.target === slideRef.current) { slideRef.current.classList.remove("slide-in-from-right"); } else if (e.target === clonedSlideRef.current) { clonedSlideRef.current.classList.remove("slide-out-to-left"); } }; useEffect(() => { fetchLatestUploads(); }, []); return ( <div className="w-full flex"> {/* Slide show section */} <div className="w-4/5 aspect-video relative overflow-hidden"> <img ref={slideRef} onLoad={handleSlideLoad} onAnimationEnd={handleAnimationEnd} className="aspect-video object-cover w-full" src={slide.poster} alt={slide.title || "轮播图片"} /> {/* 条件渲染克隆图 */} {clonedSlide.poster && ( <img ref={clonedSlideRef} onAnimationEnd={handleAnimationEnd} className="aspect-video object-cover absolute inset-0 w-full" src={clonedSlide.poster} alt={clonedSlide.title || "克隆轮播图"} /> )} <SlideShowController onNextClick={handleOnNextClick} /> </div> {/* Up Next Section */} <div className="w-1/5 aspect-video bg-red-300"></div> </div> ); } const SlideShowController = ({ onNextClick, onPrevClick }) => { const btnClass = "bg-primary rounded border-2 text-white text-xl p-2 outline-none hover:bg-opacity-90 transition-colors"; return ( <div className="absolute top-1/2 -translate-y-1/2 w-full flex items-center justify-between px-2"> <button onClick={onPrevClick} className={btnClass} type="button"> <AiOutlineDoubleLeft /> </button> <button onClick={onNextClick} className={btnClass} type="button"> <AiOutlineDoubleRight /> </button> </div> ); };
内容的提问来源于stack exchange,提问作者Farhan Khan
相关产品推荐
相关产品推荐

