我正在尝试构建一个具有覆盖流效果的轮播。我正在屏幕中渲染三个项目,循环遍历项目列表直到重新启动。我遇到的问题是转换没有按预期发生。当用户想要检查下一张图像时,中间的轮播项应该过渡到第一个位置,第一个到最后一个,最后一个到中间。但最后一个元素没有过渡,看起来它只是在屏幕上渲染而没有过渡。
Carousel.tsx:
type CarouselProps = {
children:
| React.ReactElement<CarouselItemProps>
| React.ReactElement<CarouselItemProps>[];
className?: React.HTMLAttributes<HTMLDivElement>["className"];
};
const Carousel = ({ children, className }: CarouselProps) => {
const carouselItems = Children.toArray(children);
const [currentIndex, setCurrentIndex] = useState<number>(0);
const prevIndex =
(currentIndex - 1 + carouselItems.length) % carouselItems.length;
const nextIndex = (currentIndex + 1) % carouselItems.length;
const getCarouselItemsToShow = () => {
if (carouselItems.length < 3) return carouselItems;
const indexes = [prevIndex, currentIndex, nextIndex];
return indexes.map((index) => carouselItems[index]);
};
const handleClickPrev = () => {
setCurrentIndex(
(prevState) =>
(prevState - 1 + carouselItems.length) % carouselItems.length
);
};
const handleClickNext = () => {
setCurrentIndex((prevState) => (prevState + 1) % carouselItems.length);
};
return <div className="relative flex align-items h-[279px] mb-[20px] border border-white *:absolute *:top-[50%] *:-translate-y-1/2 *:-translate-x-1/2 *:transition-all *:duration-1000">
{getCarouselItemsToShow().map((item, index) => {
return React.cloneElement(
item as React.ReactElement<CarouselItemProps>,
{
className:
index === 1
? "w-[419px] left-[50%] z-10"
: "w-[297px] opacity-80 first:left-[30%] last:left-[70%]",
}
);
})}
</div>
[...]
}
CarouselItem.tsx:
export type CarouselItemProps = {
imgSrc: string;
description: string;
className?: React.HTMLAttributes<HTMLDivElement>["className"];
};
const CarouselItem = ({ imgSrc, className }: CarouselItemProps) => {
return (
<Image
src={imgSrc}
alt=""
width={419}
height={275}
className={"rounded-[40px] " + className}
/>
);
};
export default CarouselItem;
App.tsx:
[...]
<Carousel>
<CarouselItem
imgSrc={data.jobs[0].projects[0].imageSrc}
description={data.jobs[0].projects[0].description}
/>
<CarouselItem
imgSrc={data.jobs[0].projects[1].imageSrc}
description={data.jobs[0].projects[1].description}
/>
<CarouselItem
imgSrc={data.jobs[0].projects[2].imageSrc}
description={data.jobs[0].projects[2].description}
/>
</Carousel>
[...]
这段代码的问题是,即使在组件上使用相同的键,React 也会重新渲染组件(我认为它们将被删除并在 DOM 中再次添加),因为它们是在 DOM 的不同位置创建的.
因此,为了解决这个问题,我将键保留为项目索引,但是,为了在 UI 上显示它们,我对它们进行了排序,这样我就可以将它们全部渲染在 DOM 中的同一位置,从而保持过渡。
更新了轮播组件:
export type CarouselItemProps = {
imageSrc: string;
description: string;
index: number;
};
type CarouselProps = {
className?: React.HTMLAttributes<HTMLDivElement>["className"];
carouselItems: CarouselItemProps[];
};
const Carousel = ({ carouselItems, className }: CarouselProps) => {
const [currentIndex, setCurrentIndex] = useState<number>(0);
const getPrevIndex = (currentIndex: number) =>
(currentIndex - 1 + carouselItems.length) % carouselItems.length;
const getNextIndex = (currentIndex: number) =>
(currentIndex + 1) % carouselItems.length;
const getCarouselItemsToShow = () => {
if (carouselItems.length < 3) return carouselItems;
const prevIndex = getPrevIndex(currentIndex);
const nextIndex = getNextIndex(currentIndex);
const indexes = [prevIndex, currentIndex, nextIndex].sort();
return indexes.map((index) => carouselItems[index]);
};
const handleClickPrev = () =>
setCurrentIndex((prevState) => getPrevIndex(prevState));
const handleClickNext = () =>
setCurrentIndex((prevState) => getNextIndex(prevState));
return (
<div className={className}>
<div className="relative flex align-items h-[279px] mb-[20px] *:absolute *:top-[50%] *:transition-all *:-translate-y-1/2 *:-translate-x-1/2 *:duration-1000 ">
{getCarouselItemsToShow().map((item, index) => {
let className = "left-[30%] z-0 w-[297px] opacity-80";
if (index === currentIndex)
className = "left-[50%] z-10 opacity-100 w-[419px]";
if (index === getNextIndex(currentIndex))
className = "left-[70%] z-0 w-[297px] opacity-80";
return (
<CarouselItem
key={item.index}
description={item.description}
imgSrc={item.imageSrc}
className={className}
/>
);
})}
</div>
<div className="flex items-center gap-[10px] justify-center h-[46px]">
<Arrow onClick={handleClickPrev} />
{carouselItems.map((carouselItem, index) => (
<DotIndicator
key={carouselItem.index}
isActive={index === currentIndex}
/>
))}
<Arrow rotate onClick={handleClickNext} />
</div>
<p className="text-neutral-500 mt-1">
{carouselItems[currentIndex].description}
</p>
</div>
);
};
export default Carousel;