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

React中Embla Carousel首次加载导航箭头与指示器异常问题

问题
  • React项目中基于Embla Carousel实现的轮播组件,首次加载时导航箭头和底部指示器异常:底部指示器仅显示1个(与实际图片数量不符),导航箭头完全不显示
  • 刷新页面后所有元素显示正常,图片加载无问题,可通过鼠标拖拽完成轮播操作
相关代码
export const CarouselPropiedad = () => {
  const [emblaRef, emblaApi] = useEmblaCarousel();
  const [canNext, setCanNext] = useState(false); // Cambiado a false inicialmente
  const [canPrev, setCanPrev] = useState(false);
  const [selectedIndex, setSelectedIndex] = useState(0);
  const [scrollSnaps, setScrollSnaps] = useState([]);
  const [images, setImages] = useState([]);

  const scrollPrev = useCallback(() => {
    if (emblaApi) emblaApi.scrollPrev();
  }, [emblaApi]);

  const scrollNext = useCallback(() => {
    if (emblaApi) emblaApi.scrollNext();
  }, [emblaApi]);

  const onSelect = useCallback(() => {
    if (!emblaApi) return;
    setSelectedIndex(emblaApi.selectedScrollSnap());
    setCanPrev(emblaApi.canScrollPrev());
    setCanNext(emblaApi.canScrollNext());
  }, [emblaApi]);

  // Método para cargar dinámicamente las imágenes
  const loadImages = async () => {
    const importedImages = [];
    for (let i = 1; i <= 5; i++) {
      const image = await import(`../assets/torre-promenade/torre-promenade-${i}.jpg`);
      importedImages.push(image.default);
    }
    setImages(importedImages);
  };

  useEffect(() => {
    loadImages();
  }, []);

  useEffect(() => {
    if (emblaApi && images.length > 0) {
      emblaApi.reInit(); // Reinicializar Embla Carousel después de cargar las imágenes
      emblaApi.on("select", onSelect); // Establecer el evento select después de reinicializar
      onSelect(); // Llamar a onSelect inicialmente para establecer canPrev y canNext
      setScrollSnaps(emblaApi.scrollSnapList()); // Establecer los puntos de navegación
    }
  }, [emblaApi, images, onSelect]);

  return (
    <div className="embla rounded max-w-lg">
      <div className="embla__viewport" ref={emblaRef}>
        <div className="embla__container">
          {images.map((src, index) => (
            <div key={index} className="embla__slide">
              <img
                src={src}
                alt={`torre-promenade-${index + 1}`}
                className="w-max h-max aspect-square"
              />
            </div>
          ))}
        </div>
      </div>
      <div className="flex items-center justify-center gap-2 embla_dots_container z-10">
        {scrollSnaps.map((_, index) => (
          <DotButton
            key={index}
            selected={index === selectedIndex}
            onClick={() => emblaApi && emblaApi.scrollTo(index)} // Scroll to index
          />
        ))}
      </div>
      <button
        className="embla__button embla__button--prev disabled:hidden"
        onClick={scrollPrev}
        disabled={!canPrev}
      >
        <ChevronLeft />
      </button>
      <button
        className="embla__button embla__button--next disabled:hidden"
        onClick={scrollNext}
        disabled={!canNext}
      >
        <ChevronRight />
      </button>
    </div>
  );
};
已尝试方案
  • 替换为之前正常工作的轮播组件代码,问题依旧存在
  • 添加加载状态,等待图片加载完成后再渲染轮播组件,未解决问题
解决方案

问题根源在于Embla Carousel的初始化时机早于图片加载完成,导致组件无法正确识别轮播项数量,进而引发指示器和箭头状态异常。以下是修复后的代码及关键调整点:

export const CarouselPropiedad = () => {
  // 延迟Embla初始化,直到图片加载完成
  const [emblaRef, emblaApi] = useEmblaCarousel(
    {},
    images.length > 0 ? [] : null
  );
  const [canNext, setCanNext] = useState(false);
  const [canPrev, setCanPrev] = useState(false);
  const [selectedIndex, setSelectedIndex] = useState(0);
  const [scrollSnaps, setScrollSnaps] = useState([]);
  const [images, setImages] = useState([]);

  const scrollPrev = useCallback(() => {
    emblaApi?.scrollPrev();
  }, [emblaApi]);

  const scrollNext = useCallback(() => {
    emblaApi?.scrollNext();
  }, [emblaApi]);

  const onSelect = useCallback(() => {
    if (!emblaApi) return;
    setSelectedIndex(emblaApi.selectedScrollSnap());
    setCanPrev(emblaApi.canScrollPrev());
    setCanNext(emblaApi.canScrollNext());
  }, [emblaApi]);

  const loadImages = async () => {
    const importedImages = [];
    for (let i = 1; i <= 5; i++) {
      const image = await import(`../assets/torre-promenade/torre-promenade-${i}.jpg`);
      importedImages.push(image.default);
    }
    setImages(importedImages);
  };

  useEffect(() => {
    loadImages();
  }, []);

  useEffect(() => {
    if (!emblaApi || images.length === 0) return;

    // 移除旧监听,避免重复绑定
    emblaApi.off("select", onSelect);
    emblaApi.on("select", onSelect);

    // 更新初始状态
    onSelect();
    setScrollSnaps(emblaApi.scrollSnapList());

    // 组件卸载时清理监听
    return () => {
      emblaApi.off("select", onSelect);
    };
  }, [emblaApi, images, onSelect]);

  return (
    <div className="embla rounded max-w-lg">
      <div className="embla__viewport" ref={emblaRef}>
        <div className="embla__container">
          {images.map((src, index) => (
            <div key={index} className="embla__slide">
              <img
                src={src}
                alt={`torre-promenade-${index + 1}`}
                className="w-max h-max aspect-square"
              />
            </div>
          ))}
        </div>
      </div>
      <div className="flex items-center justify-center gap-2 embla_dots_container z-10">
        {scrollSnaps.map((_, index) => (
          <DotButton
            key={index}
            selected={index === selectedIndex}
            onClick={() => emblaApi?.scrollTo(index)}
          />
        ))}
      </div>
      <button
        className={`embla__button embla__button--prev ${!canPrev ? 'hidden' : ''}`}
        onClick={scrollPrev}
        disabled={!canPrev}
      >
        <ChevronLeft />
      </button>
      <button
        className={`embla__button embla__button--next ${!canNext ? 'hidden' : ''}`}
        onClick={scrollNext}
        disabled={!canNext}
      >
        <ChevronRight />
      </button>
    </div>
  );
};

关键调整说明:

  1. 延迟Embla初始化:通过useEmblaCarousel的第二个参数,当图片未加载完成时传入null,阻止组件提前初始化,确保轮播项就绪后再完成初始化
  2. 避免重复事件绑定:每次触发useEffect时先移除旧的select事件监听,再重新绑定,防止多次绑定导致的状态混乱
  3. 状态更新时机优化:确保在Embla实例和图片都就绪后,调用onSelect更新箭头状态,并获取最新的scrollSnapList同步指示器数量
  4. 简化按钮显示逻辑:将disabled:hidden改为条件类名控制,避免依赖特定CSS规则的歧义

内容的提问来源于stack exchange,提问作者Marty CF

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 06:05:10