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

React Cover Flow轮播组件CSS过渡异常:最后元素无过渡

Cover Flow轮播组件过渡异常问题解决

我正在开发一个带Cover Flow效果的轮播组件,页面固定渲染3个元素并支持循环切换。目前遇到CSS过渡不符合预期的问题:点击切换下一张时,中间轮播项应过渡到左侧位置,左侧项过渡到右侧位置,右侧项过渡到中间位置,但右侧元素没有过渡效果,直接突兀地渲染在屏幕上。

轮播效果异常截图

相关代码

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>
[...]}

问题原因

  1. React元素复用逻辑错误:当前渲染时没有给轮播项绑定唯一key,React会根据渲染顺序(map的index)复用组件,而非轮播项本身。切换时新的右侧元素会被React识别为新元素,直接挂载到DOM,没有过渡过程。
  2. 伪类样式突变:使用first:和last:这类Tailwind伪类,切换时数组顺序变化会导致伪类对应的元素突然变更样式,无法触发平滑过渡。

解决方案

1. 绑定唯一key确保元素复用

在克隆元素时,传入轮播项的原始索引作为key,让React能正确识别每个元素:

{getCarouselItemsToShow().map((item, index) => {
  // 获取当前项的原始索引
  const originalIndex = [prevIndex, currentIndex, nextIndex][index];
  return React.cloneElement(
    item as React.ReactElement<CarouselItemProps>,
    {
      key: originalIndex,
      className: getPositionClass(index),
    }
  );
})}

2. 替换伪类为明确的位置样式函数

提取样式逻辑为函数,避免伪类带来的样式突变:

const getPositionClass = (positionIndex: number) => {
  switch(positionIndex) {
    case 0: // 左侧项
      return "w-[297px] opacity-80 left-[30%]";
    case 1: // 中间项
      return "w-[419px] left-[50%] z-10";
    case 2: // 右侧项
      return "w-[297px] opacity-80 left-[70%]";
    default:
      return "";
  }
};

修改后的核心渲染代码

const getPositionClass = (positionIndex: number) => {
  switch(positionIndex) {
    case 0:
      return "w-[297px] opacity-80 left-[30%]";
    case 1:
      return "w-[419px] left-[50%] z-10";
    case 2:
      return "w-[297px] opacity-80 left-[70%]";
    default:
      return "";
  }
};

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) => {
      const originalIndex = [prevIndex, currentIndex, nextIndex][index];
      return React.cloneElement(
        item as React.ReactElement<CarouselItemProps>,
        {
          key: originalIndex,
          className: getPositionClass(index),
        }
      );
    })}
  </div>
);

内容的提问来源于stack exchange,提问作者Felipe Eduardo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 04:52:10