Bad cumulative layout shift (CLS) with lazyloading enabled
Checks
- [X] Not a duplicate.
- [X] Not a question, feature request, or anything other than a bug report directly related to Splide. Use Discussions for these topics: https://github.com/Splidejs/splide/discussions
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.
@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.
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>
)