davidjerleke / embla-carousel

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

Enable or disable on certain screen sizes #99

Closed flayks closed 4 years ago

flayks commented 4 years ago

I'm trying to use Embla (which is fantastic btw, great job) only on mobile, so sort of disabling it on desktop when it's been enabled on mobile (i.e. window resize or orientation change).

So for instance:

  1. Load: Desktop screen size (>768) => No carousel
  2. Resize: to Mobile (<768) => Init carousel
  3. Resize: to Desktop (>768) => Destroy carousel
  4. etc…

Flickity has this cool feature where it tracks if a CSS after element has a certain content, then enables or disables it with CSS breakpoints. But it's also 51kb of JS, so Embla is def more optimized and lightweight!

Is there any way to have a similar kind of behavior with Embla without having to listen to a window resize event and embla.destroy() (which doesn't seem to completely destroy my instances btw) + embla.reInit(options) everytime? Or am I missing something?

flayks commented 4 years ago

Just for the context, this is kind of what I'm trying to achieve, with code based on this answer, but it doesn't really seem to work when resizing to a >768 screen size from mobile (it should destroy but the carousel is still "active" and has inline styles applied)

import React, { useRef, useEffect, useState } from 'react'
import EmblaCarousel from 'embla-carousel'
import { debounce } from '../utils/functions'

const Carousel = ({ children, settingsProps = '' }) => {
    const carousel = useRef(null)
    let embla = null

    // Settings
    const settings = {
        loop: true,
        ...settingsProps
    }

    // Check for Embla
    const checkEmbla = () => {
        if (window.innerWidth < 768) {
            if (embla === null) {
                embla = EmblaCarousel(carousel.current, settings)
            }
        } else {
            if (embla != null) {
                console.log('destroy')
                embla.destroy()
                embla = null
            }
        }
    }

    useEffect(() => {
        checkEmbla(embla)

        window.addEventListener('resize', () => debounce(checkEmbla(), 100))
    }, [embla])

    return (
        <div className="carousel" ref={carousel}>
            <div className="carousel__items">
                {children}
            </div>
        </div>
    )
}

export default Carousel
davidjerleke commented 4 years ago

Hello Félix (@flayks),

Thank you for your question. I think the code in this old issue you mention is probably outdated. A lot has changed with the React implementation of Embla Carousel since then.

I took some time to create a CodeSandbox that implements what you want to achieve. Here's the CodeSandbox with its related code if you want to check it out:

const EmblaCarousel = ({ children }) => {
  const [emblaRef, emblaApi] = useEmblaCarousel({ loop: false });
  const [emblaIsActive, setEmblaIsActive] = useState(false);

  const initEmblaBelow768px = useCallback(() => {
    const isActive = window.innerWidth < 768;
    setEmblaIsActive(isActive);
  }, [setEmblaIsActive]);

  useEffect(() => {
    initEmblaBelow768px();
    window.addEventListener("resize", initEmblaBelow768px);
    return () => window.removeEventListener("resize", initEmblaBelow768px);
  }, [initEmblaBelow768px]);

  return (
    <div className="embla">
      <div className="embla__viewport" ref={emblaIsActive ? emblaRef : null}>
        <div className="embla__container">
          {/* ...slides */ }
        </div>
      </div>
    </div>
  );
};

So here's what's happening:

Note

Let me know if it helps! David

flayks commented 4 years ago

Thank you so much @davidcetinkaya for your time and answer! That sounds very good. I'll have a look tomorrow and let you know.

flayks commented 4 years ago

@davidcetinkaya I tried your example and it works… partially, haha. Even on your CodeSandbox, it seems that coming from a small screen (<768, where the carousel is enabled), to the Desktop, the carousel is not really destroyed and we can still drag slides. The behavior should be that when starting on mobile, it should dynamically destroy and reinit the carousel.

davidjerleke commented 4 years ago

@flayks that’s strange. I can’t drag it and the scenario you mention works for me. Which link did you click on?

Try this link: https://isr3u.csb.app/

flayks commented 4 years ago

This exact one! Here is a video to show more context: https://streamable.com/8zzx85

davidjerleke commented 4 years ago

So strange. Thanks for the video @flayks. I’ll take a look at it and will let you know when I have something.

davidjerleke commented 4 years ago

Hello again Félix (@flayks),

So I found why this is happening. Embla will re-initialize on the resize event internally, and this resize event is debounced with 500 milliseconds. This is my understanding of the issue:

To solve this I will ship a patch fix as soon as possible. I'll have to make sure the internal re-initialize function doesn't run when the carousel has been destroyed. Nice catch 🙂!

Kindly, David

flayks commented 4 years ago

@davidcetinkaya Oh wow, good work on finding that David! I kind of saw when trying the resizing method that it was debounced, but I didn't know that it would affect the destroy method as well. Thanks for your digging. Other than that, the library is on point 👌

flayks commented 4 years ago

@davidcetinkaya hella fast! Thanks for fixing that issue, it's gonna help lots on my current project 💥

davidjerleke commented 4 years ago

Hello Félix (@flayks),

Release 4.0.2 comes with a fix for this issue. Please let me know if it's working as intended.

Best, David

davidjerleke commented 4 years ago

@flayks so this solves the issue right 🙂?

flayks commented 4 years ago

@davidcetinkaya absolutely! Works like a charm now. Thanks again!

davidjerleke commented 4 years ago

I’m glad to hear that @flayks. Thank you for opening this issue and making Embla Carousel better 👍🏻.

Best, David