rcbyr / keen-slider

The HTML touch slider carousel with the most native feeling you will get.
https://keen-slider.io/
MIT License
4.57k stars 210 forks source link

all slides shortly visible on screen right after load #364

Open welschmoor opened 10 months ago

welschmoor commented 10 months ago

I've lately built 3 different sliders with keen-slider and all 3 have this bug. Let's say we have a slider with pictures. We visit the page and for a fraction of a second we see all slides at once before they move "off screen" and become a slider.

It's especially well visible on vertical sliders with slides that have different background colors. Horizontal sliders are also affected.

Repo: https://github.com/welschmoor/ksb (yarn install, yarn dev) Video: https://youtu.be/O8LJNZnzfqE

Update: My repo above is not even necessary. I copy pasted the "vertical slider" from the examples page into my next.js project, and the bug is also there

This would be the code:

//import keen-slider css into layout or _app
'use client'

import { useKeenSlider } from 'keen-slider/react'
import React from 'react'

export default function TestPage() {
  const [sliderRef] = useKeenSlider({
    loop: true,
    slides: {
      origin: 'center',
      perView: 2,
      spacing: 10,
    },
    vertical: true,
  })

  return (
    <div ref={sliderRef} className="keen-slider" style={{ height: 300 }}>
      <div className="keen-slider__slide number-slide1 bg-blue-500">1</div>
      <div className="keen-slider__slide number-slide2 bg-red-600">2</div>
      <div className="keen-slider__slide number-slide3 bg-blue-500">3</div>
      <div className="keen-slider__slide number-slide4 bg-red-600">4</div>
      <div className="keen-slider__slide number-slide5 bg-blue-500">5</div>
      <div className="keen-slider__slide number-slide6 bg-red-600">6</div>
    </div>
  )
}

bug happens in nextjs in old /pages folder as well as in new /app dir. I don't remember seeing this bug last year.

welschmoor commented 10 months ago

Here's a "solution", more of a hackaround which I would not want to implement in production:

We only render 1 slide at first. And 800milliseconds later we increase the number of slides that we loop over to more. Also another 400 milliseconds later we update the slider.

'use client'

import { useKeenSlider } from 'keen-slider/react'
import React, { useEffect, useState } from 'react'

export default function TestPage() {
  const [children, setChildren] = useState([null])
  const [sliderRef, slider] = useKeenSlider({
    loop: true,
    slides: {
      origin: 'center',
      perView: 1,
      spacing: 0,
    },
  })

  useEffect(() => {
    setTimeout(() => {
      setChildren([null, null, null])
    }, 800)
    setTimeout(() => {
      if (slider) {
        slider.current?.update()
      }
    }, 1200)
  }, [slider])

  return (
    <div ref={sliderRef} className="keen-slider" style={{ height: 300 }}>
      {children.map((each, index) => {
        return (
          <div
            key={index}
            className={
              index % 2 === 0
                ? 'h-full keen-slider__slide number-slide2 bg-red-600'
                : 'h-full  keen-slider__slide number-slide1 w-full bg-blue-500'
            }
          >
            1
          </div>
        )
      })}
    </div>
  )
}

Real data can be sliced and the sliceIndex increased from 1 to length of data some milliseconds later: data.slice(0, sliceIndex).map(each => ...

justinhenricks commented 9 months ago

For anyone else who stumbles upon this.. I just solved by only rendering the first image until loaded, once loaded I render the rest and call an update on the instance. Also a bit hacky, but not too bad.

Like this sorta thing:


  useEffect(() => {
    if (loaded && instanceRef.current) {
      instanceRef.current?.update();
    }
  }, [loaded, instanceRef]);

      <div ref={sliderRef} className="keen-slider xl:!hidden">
        {/* Always render the first image */}
        {renderImage(media[0], 0)}

        {/* Conditionally render the remaining images if loaded */}
        {loaded && media.slice(1).map((med, i) => renderImage(med, i + 1))}
      </div>

const renderImage = (media, i: number) => {
  return (
    <div
      className={`keen-slider__slide `}
      key={med.id || image?.id}
    >
      {image && (
        <Image
          loading={i === 0 ? 'eager' : 'lazy'}
          data={image}
          aspectRatio="1"
          sizes={
            isFirst || isFourth
              ? '(min-width: 48em) 60vw, 90vw'
              : '(min-width: 48em) 30vw, 90vw'
          }
        />
      )}
    </div>
  );
};
dany68 commented 7 months ago

Hi, I wanted to up this thread. Is there any fix planned ?

I also encounter this bug in all of my nuxt apps if only one slide must be visible at a time.

Thanks

fcdigital commented 7 months ago

Ребята. Я случайно пофиксил эту проблему добавив точки для пролистывания

`

const setActiveProject = useDefinition(state=>state.setActiveProject);
const [currentSlide, setCurrentSlide] = useState(0)
const [loaded, setLoaded] = useState(false)

const [sliderRef,instanceRef] = useKeenSlider(
    {
        loop:true,
        slides:{
            origin:'auto',
            perView:1,
        },
        created(){
            setLoaded(true);
        }
    },
    [
        slider =>{
            slider.on("detailsChanged",()=>{
                if(instanceRef.current !== null){
                    setCurrentSlide(instanceRef.current.track.details.rel)
                    setActiveProject(instanceRef.current.track.details.rel)
                }
            })

        }

    ]
)

useEffect(()=>{
    instanceRef.current?.update()
},[loaded])
return (
    <div className={S.navigation_container}>
        <div className={S.slider_container}>
        <div ref={sliderRef} style={{height:'100%'}} className={'keen-slider'}>
            <div className={`${S.slide} keen-slider__slide`}><Image priority quality={100}alt='Blob' src={Blob} fill/></div>
            <div className={`${S.slide} keen-slider__slide`}><Image priority quality={100}alt='Blob' src={Blob} fill/></div>
            <div className={`${S.slide} keen-slider__slide`}><Image priority quality={100}alt='Blob' src={Blob} fill/></div>
            <div className={`${S.slide} keen-slider__slide`}><Image priority quality={100}alt='Blob' src={Blob} fill/></div>
        </div>

    </div>
    {loaded && instanceRef.current && (
        <div className={S.dots}>
          {[
            ...Array(instanceRef.current.track.details.slides.length).keys(),
          ].map((idx) => {
            return (
              <button
                key={idx}
                onClick={() => {
                  instanceRef.current?.moveToIdx(idx)
                }}
                className={currentSlide === idx && S.dot__active || S.dot}
              ></button>
            )
          })}
        </div>
        )}
    </div>