Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add watchResize and watchSlides options #159

Closed
1 task done
Tracked by #321
ej-mitchell opened this issue Mar 23, 2021 · 13 comments · Fixed by #449
Closed
1 task done
Tracked by #321

Add watchResize and watchSlides options #159

ej-mitchell opened this issue Mar 23, 2021 · 13 comments · Fixed by #449
Labels
core This is related to the core package feature request New feature or request react Issue is related to React resolved This issue is resolved svelte Issue is related to Svelte vue Issue is related to Vue

Comments

@ej-mitchell
Copy link

ej-mitchell commented Mar 23, 2021

Bug is related to

  • embla-carousel-react

Embla Carousel version

  • v4.2.0

Describe the bug

  • When I first populate the carousel with several items, I am unable to use the navigation arrows or to drag through my items.
  • When I resize the window to a different size (either larger or smaller), then restore it to its original size, then the arrows and drag functionality works.
  • When I log embla (which is set to useEmblaCarousel()) before I do the resizing, the object is not null or undefined.
  • I'm still trying to determine whether there are parent styles (in overarching components) causing this issue, but I can't see anything in these components that would cause the dragging and scrollNext/scrollPrev methods to not work.

CodeSandbox

  • Unfortunately, I'm working for a client and am unable to share the full code. However, I scrubbed the important variable names and have the carousel code below. (I'm using styled components.)
const OverflowContainer = styled.div`
    overflow: hidden;
    height: 100%;
    min-width: 80%;
`;

const FlexContainer = styled.div`
    display: flex;
    height: 100%;
    width: 80%;
`;

const Slide = styled.div`
    position: relative;
    flex: 0 0 15%;
    height: 80%;
`;

const PreviousButton = styled.button`
    cursor: pointer;
    background-color: green;
    width: 30px;
    height: 30px;
`;

const NextButton = styled.button`
    cursor: pointer;
    background-color: blue;
    width: 30px;
    height: 30px;
`;

interface Props {
    slides: JSX.Element[];
}

const ExampleCarousel = ({mergedVideos}: Props) => {
    const [carouselRef, embla] = useEmblaCarousel();

    const scrollPrev = useCallback(() => {
        embla && embla.scrollPrev(); // embla evaluates properly here - failure happens in scrollPrev()?
    }, [embla]);
    const scrollNext = useCallback(() => {
        embla && embla.scrollNext(); // embla evaluates properly here - failure happens in scrollNext()?
    }, [embla]);

    const slideVideos = slides.map((m) => <Slide key={m.key}>{m}</Slide>);

    return (
        <>
            <OverflowContainer ref={carouselRef}>
                <FlexContainer>{slideVideos}</FlexContainer>
            </OverflowContainer>
            <PreviousButton onClick={scrollPrev} />
            <NextButton onClick={scrollNext} />
        </>
    );
};

export default ExampleCarousel;

Steps to reproduce

  1. Populate the carousel with enough items to merit scrolling
  2. Clicking the navigation buttons will not do anything.
  3. Dragging the carousel items to scroll will not do anything.
  4. Resize the window and then put it back to its original size.
  5. You should be able to use the navigation buttons and drag to scroll.

Expected behavior

  • I expect the scrolling behaviors to work immediately, and not after resizing my window.

Thank you! Let me know if there's anything else I can provide... If this weren't a client project, I would have gifs and other accompanying pieces for you. :( Apart from this strange issue, I've loved using Embla so far!

@ej-mitchell ej-mitchell added the bug Something isn't working label Mar 23, 2021
@davidjerleke
Copy link
Owner

davidjerleke commented Mar 23, 2021

Hi EJ (@ej-mitchell),

Thank you for the clear issue description. I can't be 100% sure because I don't have access to your full setup, but I don't think this is a bug. I think this is happening because one of the following reasons:

  1. Embla Carousel is initialized before it has any slides to pick up. Embla will do a querySelector to check for any slides inside its container, so if there’s a very brief moment that you probably won’t even notice, that will be enough to cause the issue.
  2. Any parent of the carousel has display: none or similar when it's initialized the first time (like a hidden modal or similar). This makes it impossible for Embla to pick up any slide or container dimensions.

Embla Carousel has no internal mechanism for picking up any changes in slides. This has to be done manually by telling Embla to reinitialize. The reason why it works when you resize the window is because Embla automatically calls embla.reInit() on window resize, which will do a hard reset and pick up any slides inside its container.

If this is happening because of reason 1, try the following solution:

useEffect(() => {
  if (!embla) return 
  embla.reInit() // If the slides prop changes, pick it up
}, [
  embla,
  slides /* Add slides as a dependency to trigger this when the prop changes. If you're mapping the children prop to slides, you can add children to the dependency array instead. */
])

If it's happening because of reason 2, you can either go with solution 1 and run embla.reInit() when the carousel becomes visible (like when the modal has opened), or pass null instead of the carouselRef until you've removed the display: none style. This will work because Embla will reinitialize when the ref changes. Something along these lines:

const [carouselReady, setCarouselReady] = useState(false)

return (
  ...

  <OverflowContainer ref={carouselReady ? carouselRef : null}>
    <FlexContainer>{slideVideos}</FlexContainer>
  </OverflowContainer>

  ...
)

Let me know if it helps.

Best,
David

@ej-mitchell
Copy link
Author

ej-mitchell commented Mar 23, 2021

Thank you so much, @davidjerleke! This was a lovely and concise answer. I will give that a go. 😸 I will close the issue, too since this is not a bug.

@davidjerleke
Copy link
Owner

@ej-mitchell let me know how it goes when you've tried my suggestions 👍.

@ej-mitchell
Copy link
Author

It worked 😻 thank you! (I had a hunch that it was scenario 1)

@davidjerleke
Copy link
Owner

Thanks for confirming @ej-mitchell 👍. Enjoy!

@richgcook
Copy link

Sorry to bring this back up @davidjerleke but do you know if there's a similar solution for Vue v3?

@davidjerleke
Copy link
Owner

davidjerleke commented Aug 4, 2022

Hi @richgcook,

Thank you for your question. You didn't provide any details with your question?

  • Are you using the embla-carousel-vue package?
  • Are you using a slot for your slides or a foreach?

Please note that I have very little experience with Vue so you are probably better off solving this instead of me. But what you need to do is the following: As soon as the slide nodes inside the container change, run this:

if (emblaApi.value) emblaApi.value.reInit()

I hope it helps.

Best,
David

@richgcook
Copy link

Thanks @davidjerleke I opened up a separate issue for this to supply some more details...

@davidjerleke
Copy link
Owner

davidjerleke commented Aug 7, 2022

Hi guys, (@ej-mitchell, @richgcook, @DarceyLloyd and more...),

The problem

Devs using any of the Embla Carousel wrappers like embla-carousel-react, and embla-carousel-vue seem to get this problem a lot where:

  1. The carousel is initialized with an empty container before there are any slides to pick up (which happens for a very very brief moment, barely noticeable).
  2. The component re-renders and populates the container with slides.
  3. Embla doesn't reInit() so devs are confused and realize that the carousel only starts working when the window is resized, because Embla automatically runs reInit() then.
  4. They think this is a bug even though this is stated in the docs:

This guide will show you how to pick up slide changes whether you're adding or removing slides. Embla Carousel has no internal mechanism for picking up any changes in slides. This has to be done manually,

I'm getting a lot of "false" bug reports about this:

...to mention a few. Additionally, devs using Embla in modals are having trouble misplaced scroll snaps (because you have to run reInit() for Embla to re-calculate stuff when dimension change and the carousel becomes visible.

Proposed solutions

I'm suggesting using MutationObserver to solve this and listen for type === 'childList' and added/removed nodes. If slide nodes have been added or removed, Embla should automatically run reInit(). Maybe it can listen for width or height changes with ResizeObserver too.

I can think of two solutions:

1. Create a plugin called embla-carousel-watch-slides

React

import React from 'react'
import useEmblaCarousel from 'embla-carousel-react'
import WatchSlides from 'embla-carousel-watch-slides'

export const EmblaCarousel = () => {
  const [emblaRef] = useEmblaCarousel({ loop: false }, [WatchSlides()])

Vue

<script>
  import emblaCarouselVue from 'embla-carousel-vue'
  import WatchSlides from 'embla-carousel-watch-slides'

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

Pros: Can be used with any framework/library wrapper and also the vanilla package.
Cons: Devs have to install the plugin.

2. Add an argument to the library wrappers

React

import React from 'react'
import useEmblaCarousel from 'embla-carousel-react'
import Autoplay from 'embla-carousel-autoplay'

export const EmblaCarousel = () => {
  const [emblaRef] = useEmblaCarousel({ loop: false }, [Autoplay()], true) // <-- the last boolean here is watchChildren (what I'm proposing)

Vue

<script>
  import emblaCarouselVue from 'embla-carousel-vue'
  import Autoplay from 'embla-carousel-autoplay'

  export default {
    setup() {
      const [emblaNode, emblaApi] = emblaCarouselVue({ loop: false }, [Autoplay()], true)  // <-- the last boolean here is watchChildren (what I'm proposing)

@xiel sorry for tagging you but if you're interested, feel free to chip in. But please don't feel obligated to, only if you want 🙂.

Let me know what you think about the suggested solutions. If you guys have completely different solutions you want to share, feel free to do so. Thanks.

Best,
David

@emjayschoen
Copy link

emjayschoen commented Mar 13, 2023

For any future devs running into this issue, I was able to solve it in React with the help of @react-hook/resize-observer going off of @davidjerleke's observer idea:

import useResizeObserver from '@react-hook/resize-observer';

const EmblaCarousel = ({slides, options}) => {
  const [emblaNode, emblaApi] = useEmblaCarousel();

  const handleResize = (entry) => {
    emblaApi.reInit();
  };

  const rootNode = emblaApi?.rootNode() || null;
  useResizeObserver(rootNode, handleResize);

  ...

In my case, the issue was showing a loading screen before showing the carousel, which was causing the initial size of slides to be 0. The resize observer automatically calls reInit once the carousel eventually populates, solving the issue.

@davidjerleke davidjerleke added vue Issue is related to Vue svelte Issue is related to Svelte and removed resolved This issue is resolved not a bug This issue is not a bug labels Mar 18, 2023
@davidjerleke davidjerleke reopened this Mar 18, 2023
@davidjerleke davidjerleke changed the title Scroll/Drag doesn't work until window is resized Make children/slides reactive Mar 18, 2023
@davidjerleke davidjerleke added the feature request New feature or request label Mar 18, 2023
@davidjerleke davidjerleke added the upcoming A feature or bug fix is on its way for this issue label Mar 29, 2023
@davidjerleke davidjerleke linked a pull request Mar 29, 2023 that will close this issue
@davidjerleke davidjerleke mentioned this issue Mar 31, 2023
37 tasks
@davidjerleke davidjerleke added the core This is related to the core package label Mar 31, 2023
davidjerleke added a commit that referenced this issue Apr 4, 2023
@davidjerleke davidjerleke changed the title Make children/slides reactive Add watchResize and watchSlides options Apr 24, 2023
@davidjerleke
Copy link
Owner

Feature specification

watchResize

Embla will no longer listen to the window resize event. Instead, it will use ResizeObserver on its container and slides. If any size has changed it will re-initialize by calling reInit automatically. Default is:

{
  watchResize: true,
}

You can opt out of the default Embla resize behaviour like so:

{
  watchResize: false,
}

However, you can also pass a callback function to watchResize like so:

{
  watchResize: (emblaApi: EmblaCarouselType, entries: ResizeObserverEntry[]) => {
    // do your own thing here BEFORE Embla runs its internal resize logic

    return true // <-- Return true if you want Embla to continue with its default resize behaviour

    // ...OR:

    return false // <-- Return false if you want Embla to skip its default resize behaviour
  },
}

watchSlides

This new option adds the feature of Embla automatically re-initializing by calling reInit when slides has been added or removed, using MutationObserver. Default is:

{
  watchSlides: true,
}

You can opt out of the default Embla slides changed behaviour like so:

{
  watchSlides: false,
}

However, you can also pass a callback function to watchSlides like so:

{
  watchSlides: (emblaApi: EmblaCarouselType, mutations: MutationRecord[]) => {
    // do your own thing here BEFORE Embla runs its internal slides changed logic

    return true // <-- Return true if you want Embla to continue with its default slides changed behaviour

    // ...OR:

    return false // <-- Return false if you want Embla to skip its default slides changed behaviour
  },
}

@davidjerleke
Copy link
Owner

@ej-mitchell, @emjayschoen, @richgcook this feature has been released with v8.0.0-rc01.

@girdhariag
Copy link

girdhariag commented Mar 8, 2024

I am still facing this issue and I am using the version 8.0.0-rc17.

I tried all the different ways to solve this as mentioned in the above suggestions but somehow it is still the same.

Anyone else still facing this issue?

@davidjerleke davidjerleke added resolved This issue is resolved and removed upcoming A feature or bug fix is on its way for this issue labels Mar 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core This is related to the core package feature request New feature or request react Issue is related to React resolved This issue is resolved svelte Issue is related to Svelte vue Issue is related to Vue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants