7

I'm using this carousel with indicator buttons from daisyUI in a Nextjs app.

As can be observed in their demo, when clicking on an indicator button, apart from switching slides the page will also scroll such that the top border of the slide is at the top of the screen.

Is there a way of using this component while preventing the scroll?

4
  • 1
    Not familiar with the daisyUI library, but the problem is that the bottom navigation uses IDs, which by default when clicked scroll into view. Most likely you need to find a way to attach an event listener to each button and use event.preventDefault() to stop the browser's default behavior. See more here: developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault
    – micahlt
    Commented Jan 29, 2023 at 5:24
  • I didn't understand your question, could you be more clear as of what you want to achieve and what is presently there in the code ,by providing some code along with it Commented Jan 29, 2023 at 5:34
  • @micahlt The problem with what you mention is that it seems that the Carousel in daisyUI reacts to the change in the URL so if you put an event.preventDefault() it doesn't scroll but it doesn't change the image on the carousel either. I'm having the same issue as the OP
    – RabidTunes
    Commented Feb 25, 2023 at 23:04
  • I've made the following stackblitz to reproduce the problem @Winkelried can you update the question adding this reproducible project so other people can check it and help us? stackblitz.com/edit/…
    – RabidTunes
    Commented Feb 26, 2023 at 10:12

5 Answers 5

6

I think I figured this one out. The key here is to consider that the carousel is an element with a horizontal scroll. This means that one way of tackling this is to manually displace the horizontal scroll by using a Javascript function.

I don't know how this is done in Next.js, but the idea is to define a function like this:

let carouselElement = . . .; // carouselElement has to hold the HTMLElement of the carousel


function scrollCarousel(targetImageNumber) {
    let carouselWidth = carouselElement.clientWidth;

    // Images are numbered from 1 to 4 so thats why we substract 1
    let targetImage = targetImageNumber - 1;

    let targetXPixel = (carouselWidth * targetImage) + 1

    carouselElement.scrollTo(targetXPixel, 0);
}

You can then replace the <a> tags by <button> tags and add an onClick handler that performs the scroll manually on the Carousel HTML Element, like this:

<div class="flex justify-center w-full py-2 gap-2">
    <!-- You'll have to pass the number image to the function -->
    <button onClick={scrollCarousel(1)} class="btn btn-xs">1</button> 
    <button onClick={scrollCarousel(2)} class="btn btn-xs">2</button> 
    <button onClick={scrollCarousel(3)} class="btn btn-xs">3</button> 
    <button onClick={scrollCarousel(4)} class="btn btn-xs">4</button>
</div>

I built a Stackblitz example (in Svelte, sorry) with the solution here

4

I experienced the same issue but was able to work around it using element.scrollIntoView.

By default, it has the same issue as the fragment solution that OP references but if you call it with: slide2.scrollIntoView({ block: 'nearest', inline: 'center' }) it doesn't move vertically but does the smooth horizontal scroll. MDN docs

In my opinion, this is a bit simpler than the solution where you calculate x-coordinates, though you do need to get a reference to the next/previous slide element. How you do so will probably depend on what, if any, JS framework you're using.

0

CLS Cumulative Layout Shift 🔗 https://web.dev/cls/

*ℹ️ Haven't tested a thing yet (finishing prioritized tasks atm, will be back for this one). Maybe the following list can bring some light or/and leads to more understanding about what's going on. In general it doesn't feel like a situation where I want to add heavy scripting, feels like there is a elegant way... (maybe it's even against the library paradigm, as we have target-selectors for sidemenu's (no scripting seem to be the approach here).

Here some first thoughts, guesses, experiences, investigations and links, and the IMO root cause why it happens..

Let's go:

  1. At firsrt glance it looks like anchor jumping by id, e.g. #slide1 jumps to id="slide1"element, native anchor jumps, so preventDefault will stop everything (let's exclude that)

Assuming the root cause are unknown width and heigh of the images (we need safeNumbers, percentage and vw,vh in safeNumbers no percentages etc. would probably work, but we need percentages 🥳  

  1. Check if animation targets top, right, bottom, left and if so, use: `transform: translate() instead. Just comes to mind: DaisyUI and HeadlessUI is a great fit here.  

  2. Could eventually move the issue to loading time:

img { max-width: 100%; height: auto; }

...but don't we preload images in carousels anyway? if so, then the dimensions of the image is known – unknown width/height of the image as the root cause – and useLayoutEffect() is where you an calculate and set px values before the render is painted to the screen, right?

  1. Aspect Ratio Solution

good luck (will fix the typos another day ^^ when I pick up your end solution hehe)

0

For anyone dealing with this still, this worked for me in react:

const goTo = (event) => {
    event.preventDefault()
    const btn = event.currentTarget;

    //Equivalent
    //const carousel = document.querySelector('.carousel')
    // const carousel = btn.parentElement!.parentElement!.parentElement!
    const carousel = document.querySelector('#imageCarousel')

    const href = btn.getAttribute('href')
    const target = carousel.querySelector(href)
    const left = target.offsetLeft
    carousel.scrollTo({ left: left })
}

<div className="absolute flex justify-between transform -translate-y-1/2 left-5 right-5 top-1/2">
   <>
       <a onClick={goTo} href={"#slide" + last} className="btn btn-circle">❮</a>
       <a onClick={goTo} href={"#slide" + next} className="btn btn-circle">❯</a>
   </>
</div>

I included the code above for each a tag that controls the switching of slides.

Set the "#imageCarousel" id in the "goTo" function to match the id of your daisy ui carousel. If you don't have an id, add one.

0

a little bit late but this is a tested solution for this case:

import { useEffect, useRef, useState } from "react";

const Carousel = ({ banners, autoplayInterval = 5000 }) => {
    const [currentSlide, setCurrentSlide] = useState(0);
    const [isAutoplayActive, setIsAutoplayActive] = useState(true);
    const carouselRef = useRef(null)

    const slideTo = (targetImageNumber) => {
        let carouselWidth = carouselRef.current?.clientWidth;
        let targetXPixel = (carouselWidth * targetImageNumber) + 1
        carouselRef.current?.scrollTo(targetXPixel, 0);
    }

    const handleNextSlide = () => {
        const nextIndex = (currentSlide + 1) % banners.length;
        setCurrentSlide(nextIndex);
        slideTo(nextIndex)
    };

    const handlePreviousSlide = () => {
        const prevIndex = (currentSlide - 1 + banners.length) % banners.length;
        setCurrentSlide(prevIndex);
        slideTo(prevIndex)
    };
    
    useEffect(() => {
        if (!isAutoplayActive) return; 
    
        const intervalId = setInterval(handleNextSlide, autoplayInterval);
    
        return () => clearInterval(intervalId);
      }, [currentSlide, isAutoplayActive, autoplayInterval]);

    return <div className="carousel w-full" ref={carouselRef}>
        {banners.map((b, idx)=><div key={idx} id={`slide${idx}`} className={`carousel-item relative w-full transition ease-in-out duration-700`}>
      <img src={b.url} className="w-full" />
      <div className="absolute flex justify-between transform -translate-y-1/2 left-5 right-5 top-1/2">
        <a onClick={handlePreviousSlide} className="btn btn-circle">❮</a> 
        <a onClick={handleNextSlide} className="btn btn-circle">❯</a>
      </div> 
    </div> )}
  </div>
  
}

export default Carousel

Not the answer you're looking for? Browse other questions tagged or ask your own question.