Splidejs / splide

Splide is a lightweight, flexible and accessible slider/carousel written in TypeScript. No dependencies, no Lighthouse errors.
https://splidejs.com
MIT License
4.83k stars 418 forks source link

Bad cumulative layout shift (CLS) with lazyloading enabled #1091

Open Dekadinious opened 1 year ago

Dekadinious commented 1 year ago

Checks

Version

v4.1.3

Description

There seems to be a case of a large cumulative layout shift in the Splide-slider when lazyloading images. The height of the images is not reserved. We are changing all our Slick-sliders to Splide, but this is kind of a deal breaker. The height and width of the images should be reserved so that lazy loading works without cumulative layout shifts and with padding set on the slider.

Reproduction Link

https://jsfiddle.net/Dekadinious/29d5mtr1/1/

Steps to Reproduce

Create a lazy-loaded slider with a lot of images. Flick fast through the slides. Observe the flash of unstyled content. If added to a page with a lot of other elements, you should also see the cumulative layout shift happening.

The fiddle I have linked to is loading the images very fast, but you can clearly see the text visible and then getting pushed down when flicking fast through the slides. It is very apparent with large layout shifts in our staging environment. The whole slider increases in height and pushes other content down as soon as the first image is loaded.

Expected Behaviour

I would expect the slider to reserve the height and width of the image so no layout shift happens.

MartinHaun commented 1 year ago

@Dekadinious

If your use case allows it, a workaround for now is to set the width and height for the item images through CSS (with breakpoint definitions).

If the images are not 'fixed' sizes for various use cases with multiple instances of the slider, then this of course won't work as intended.

magoz commented 1 year ago

I ended up using CSS aspect-ratio since I have access to the images width and height.


const mostVerticalMediaAspectRatio = images.reduce((acc, m) => {
    const itemAspectRatio = m.width / (m.height || 1)
    return acc > itemAspectRatio ? itemAspectRatio : acc
}, 1)

return (
  <Splide
    ref={mainRef}
    options={{
      type: 'loop',
      rewind: true,
      pagination: false,
      gap: '2rem'
    }}
    className="align-middle [&_#splide01-list]:items-center"
    style={{ aspectRatio: mostVerticalMediaAspectRatio.toString() }}
  >
    {images.map(m => {
      return (
        <SplideSlide key={m.src} className="flex justify-center">
          <Image
            width={m.width}
            height={m.height}
            className="h-full w-full object-contain object-center"
            src={m.src}
            alt={`image for ${title}`}
          />
        </SplideSlide>
      )
    })}
  </Splide>
)