davidjerleke / embla-carousel

A lightweight carousel library with fluid motion and great swipe precision.
https://www.embla-carousel.com
MIT License
5.8k stars 175 forks source link

reInit() required on nuxt3/vue3 applications #345

Closed richgcook closed 1 year ago

richgcook commented 2 years ago

Bug is related to

Embla Carousel version

Describe the bug

Within my nuxt3 application on load the embla carousel works fine but when navigation away and back (using NuxtLink, for example) the slider looks initialised but you cannot swipe/drag/use the slider. If I resize the browser it works as usual again.

I have read about using the reInit() method on other issues but having tried watching embla or scrollSnaps etc it doesn't seem to work.

I have also tried the vue wrapper but get the same issue. Weirdly I never had this issue in nuxt2/vue2.

CodeSandbox

<script setup>

import { ref, onMounted, watch } from 'vue'
import EmblaCarousel from 'embla-carousel'

const props = defineProps({
    slides: Array
})

const viewport = ref(null)

const embla = ref(null)
const scrollSnaps = ref([])
const selectedScrollSnap = ref(0)
const canScrollPrev = ref(false)
const canScrollNext = ref(true)

onMounted(() => {
    embla.value = EmblaCarousel(viewport.value, {
        loop: false
    })
    scrollSnaps.value = embla.value.scrollSnapList()
})

watch(??????, () => {
    console.log('hello')
    embla.value?.reInit()
})

</script>
davidjerleke commented 2 years ago

Hi @richgcook,

Please not that as I mentioned, my experience with Vue is very limited. So if any seasoned Vue dev stumbles upon this issue they should feel free to chip in.

Thank you for your question. I found this StackOverflow thread that explains how to listen for prop changes:

watch(() => props.slides, () => {
  emblaApi.value?.reInit()
});

Maybe that's what you're looking for?

On a side note

I would recommend you to use the embla-carousel-vue package because it does some of the heavy lifting for you:

Let me know if it helps.

Best, David

richgcook commented 2 years ago

Thanks so much, @davidjerleke

What's strange is that using that code snippet, which I've used previously, never fires.

I'll definitely switch to embla-carousel-vue and go from there but it's bizarre but appreciate you don't have much experience in vue... just thought there would be more out there with the same issue.

davidjerleke commented 2 years ago

@richgcook thank you for the additional details. People seem to run this problem every now and then so I'm going to consider creating a feature request suggesting to add this feature to all the framework/library wrappers. I'll let you know how it goes.

In the meantime, you might find some help if you ask this question on StackOverflow or Reddit. Additionally, if you don't mind, you can drop a CodeSandbox with your setup demonstrating the problem here in this issue.

Best, David

davidjerleke commented 2 years ago

Hi again @richgcook,

If you add the following code to your setup, does it work as expected?

const observer = new MutationObserver((mutations) => {
  const childMutations = mutations.filter((mutation) => {
    if (mutation.type !== "childList") return false;
    return mutation.addedNodes.length || mutation.removedNodes.length;
  });

  if (childMutations.length) emblaApi.value.reInit();
});

observer.observe(emblaApi.value.containerNode(), { childList: true });

If yes, I might add it to all library/framework wrappers including embla-carousel-vue.

Best, David

richgcook commented 2 years ago

Hi @davidjerleke thanks for your help and support on this.

Unfortunately this doesn't seem to help. observer doesn't return anything. I'm assuming this is for the onMounted method but do let me know!

davidjerleke commented 2 years ago

@richgcook thank you for trying. The following is working with React and Svelte: Both wrappers are picking up when the slides are mapped from props and when the slide props change. They also successfully pick up any added/removed slides.

At this stage I think that there's something unexpected/funky going on with your setup.

I'm assuming this is for the onMounted method but do let me know!

Yes, like this:

<script>
  import { onMounted } from "vue";
  import emblaCarouselVue from "embla-carousel-vue";

  export default {
    setup() {
      const [emblaNode, emblaApi] = emblaCarouselVue({ loop: false });

      onMounted(() => {
        if (emblaApi.value) {
          const observer = new MutationObserver((mutations) => {
            const childMutations = mutations.filter((mutation) => {
              if (mutation.type !== "childList") return false;
              return mutation.addedNodes.length || mutation.removedNodes.length;
            });

            if (childMutations.length) emblaApi.value.reInit();
          });

          observer.observe(emblaApi.value.containerNode(), { childList: true });
        }
      });

      return { emblaNode, emblaApi };
    },
  };
</script>

What happens if you change:

<script setup>

...into:

<script>

?

If that doesn't work, I suggest that you to create a reduced CodeSandbox with your setup that demonstrates the problem. With the sandbox, it will be way easier to debug and see why this is happening. Because I'm blindly guessing here when there could be literally anything causing this when I don't have your setup available. Thanks!

Best, David

davidjerleke commented 2 years ago

@richgcook any news on this?

richgcook commented 2 years ago

Hi @davidjerleke apologies greatly for the delay.

I'm using the <script setup></script> sugar for vue but nevertheless I couldn't get your suggested fix to work. I'll try to create a CodeSandbox asap.

richgcook commented 1 year ago

Hi @davidjerleke sorry I'm struggling for time to get a CodeSandbox box. Apologies. I've been doing some tests, however.

Within onMounted I've fired the init event with the internalEngine() method to see what it was returning between a browser refresh and an internal route (when it doesn't work unless resized).

embla.value.on('init', () => {
    console.log(embla.value.internalEngine())
})

Screenshot 2022-09-23 at 11 56 37@2x

We can see from the screenshot that the scrollSnaps and containerRect are returning 0 as well as the limit returning NaN.

Just thought I'd share in case you have any thoughts...

davidjerleke commented 1 year ago

Hi @richgcook,

Thank you for the additional details.

We can see from the screenshot that the scrollSnaps and containerRect are returning 0 as well as the limit returning NaN.

Yes, it tells me that by the time Embla carousel is initialized, there are no slides for Embla to pick up inside its container. Ifg you get the time to create a CodeSandbox demonstrating the problem, I'll debug it further.

Best, David

davidjerleke commented 1 year ago

Hi @richgcook,

I'm closing this until there's a reduced test case that clearly demonstrates the problem (a CodeSandbox or similar). Because I can't debug fragments of code that are shared in comments.

Thank you for understanding.

Best, David