embla-carousel icon indicating copy to clipboard operation
embla-carousel copied to clipboard

reInit() required on nuxt3/vue3 applications

Open richgcook opened this issue 2 years ago • 8 comments

Bug is related to

  • [ ] embla-carousel (core package)

Embla Carousel version

  • v7.0.0-rc05

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>

richgcook avatar Aug 04 '22 13:08 richgcook

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:

  • It destroys the instance automatically when the component unmounts.
  • If you use reactive options and plugins, for example by declaring them as refs and reassign them, the carousel instance will reInit() automatically when options and/or plugins change.
  • It exposes a ref to attach to your DOM so you don't have to declare it yourself.

Let me know if it helps.

Best, David

davidjerleke avatar Aug 05 '22 06:08 davidjerleke

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.

richgcook avatar Aug 05 '22 13:08 richgcook

@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 avatar Aug 05 '22 19:08 davidjerleke

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

davidjerleke avatar Aug 06 '22 20:08 davidjerleke

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!

richgcook avatar Aug 08 '22 09:08 richgcook

@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 avatar Aug 08 '22 09:08 davidjerleke

@richgcook any news on this?

davidjerleke avatar Aug 22 '22 09:08 davidjerleke

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 avatar Sep 06 '22 22:09 richgcook

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...

richgcook avatar Sep 23 '22 11:09 richgcook

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 avatar Sep 25 '22 11:09 davidjerleke

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

davidjerleke avatar Oct 03 '22 19:10 davidjerleke