在轮播组件上应用 css 过渡时出现问题

问题描述 投票:0回答:1

我正在尝试构建一个具有覆盖流效果的轮播。我正在屏幕中渲染三个项目,循环遍历项目列表直到重新启动。我遇到的问题是转换没有按预期发生。当用户想要检查下一张图像时,中间的轮播项应该过渡到第一个位置,第一个到最后一个,最后一个到中间。但最后一个元素没有过渡,看起来它只是在屏幕上渲染而没有过渡。

Carousel result

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>
[...]
css reactjs tailwind-css carousel
1个回答
0
投票

这段代码的问题是,即使在组件上使用相同的键,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;
© www.soinside.com 2019 - 2024. All rights reserved.