swiper icon indicating copy to clipboard operation
swiper copied to clipboard

Swiper CLS (Cumulative Layout Shift) on PageSpeed Insights

Open maxymczech opened this issue 3 years ago • 48 comments

This is a (multiple allowed):

  • [ ] bug

  • [x] enhancement

  • [ ] feature-discussion (RFC)

  • Swiper Version: 6.1.2

  • Platform/Target and Browser Versions: desktop Chrome

  • Live Link or JSFiddle/Codepen or website with isssue: https://wannacat.org/

What you did

Swiper is working perfectly, thank you very much for working on this project. I am trying to increase PageSpeed performance index, and the Lighthouse testing tool in Chrome reports large CLS (Cumulative Layout Shift) due to Swiper.

Expected Behavior

It would be great to have 0 CLS due to Swiper

Actual Behavior

Reported CLS is 0.874: https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Fwannacat.org%2F&tab=desktop I have tested it and it is unfortunately due to Swiper. During the initialization, the Swiper container first disappears and then reappears, causing large layout shift. Is there any way to avoid this? Thank you.

maxymczech avatar Dec 23 '20 13:12 maxymczech

I have the same issue and seems like the problem is with the loop (at least in my case). The layout shift is caused by the slides added before and after your slides on initialisation. As per the Swiper API docs: Also, because of nature of how the loop mode works, it will add duplicated slides.

norbertorok92 avatar Jan 12 '21 10:01 norbertorok92

I can also confirm that CLS is caused by loop. Maybe as a solution for a future releases would be adding an option to manually add placeholders for duplicated slides? Before initialization the element would have the width/height set? So it won't cause this problem? Just thinking out loud

Falcikas avatar Jan 14 '21 12:01 Falcikas

I can also confirm that CLS is caused by loop

It actually did not occur to me to try turning loop off. Yes, CLS is 0 with option loop: false.

maxymczech avatar Jan 14 '21 12:01 maxymczech

If I am not mistaken the issue CLS is when you attempt to click on an item and it moves away. Drastic height changes can probably trigger it, which is what happens with Swiper.

So, with Swiper, before it loads usually all images are stacked on top of each other.

Once it's loaded they are all stacked side by side, causing a sudden abrupt change on layout.

Setting a max height with the size of the first image should fix it, but i haven't tried it yet. If anyone can confirm please let me know, I'll give it a try this weekend.

On Thu, Jan 14, 2021, 09:34 Maksym Shcherban [email protected] wrote:

I can also confirm that CLS is caused by loop

It actually did not occur to me to try turning loop off. Yes, CLS is 0 with option loop: false.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/nolimits4web/swiper/issues/4076#issuecomment-760168226, or unsubscribe https://github.com/notifications/unsubscribe-auth/AC6UQSJZ57Q5VNDGTM7QS23SZ3QDPANCNFSM4VHANYSA .

dpw1 avatar Jan 14 '21 12:01 dpw1

@dpw1 I checked my carousel and it has max height, so the issue is not caused by that. Also CLS is not only when you try to click on the item and it moves away (ref: https://web.dev/cls/)

Here is an example: https://swiperjs.com/demos/#loop_mode_infinite_loop

I think that the CLS happens when Swiper adds an extra slide in the swiper-wrapper(see example above). Initially swiper-wrapper has 10 slides. Once Swiper initialises it adds one before the first child and one after the last child.

Adding one after the last child is not an issue. The problem is that it adds one before the first one. So the first slide shifts, basically the first becomes the second.

norbertorok92 avatar Jan 14 '21 13:01 norbertorok92

When it adds an extra slide it doesn't actually cause a visual disruption, does it?

quoting from the link you have referred to:

"A layout shift occurs any time a visible element changes its position from one rendered frame to the next."

If the extra slides are off screen I'm unsure whether it could be the cause

On Thu, Jan 14, 2021, 10:05 Norbert Torok [email protected] wrote:

@dpw1 https://github.com/dpw1 I checked my carousel and it has max height, so the issue is not caused by that. Also CLS is not only when you try to click on the item and it moves away (ref: https://web.dev/cls/)

Here is an example: https://swiperjs.com/demos/#loop_mode_infinite_loop

I think that the CLS happens when Swiper adds an extra slide in the swiper-wrapper(see example above). Initially swiper-wrapper has 10 slides. Once Swiper initialises it adds one before the first child and one after the last child.

Adding one after the last child is not an issue. The problem is that it adds one before the first one. So the first slide shifts, basically the first becomes the second.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/nolimits4web/swiper/issues/4076#issuecomment-760184669, or unsubscribe https://github.com/notifications/unsubscribe-auth/AC6UQSJYNTKY6LLFHLW35MLSZ3T2VANCNFSM4VHANYSA .

dpw1 avatar Jan 14 '21 13:01 dpw1

Yes that is right, it does not causes a visual disruption... But if I run the performance profiling, under the experience summary I can see this:

image

I just try to find an answer and fix the problem. 🤷‍♂️

norbertorok92 avatar Jan 14 '21 13:01 norbertorok92

Its because of the dynamic DOM element. I think any cloned element added to tree causes CLS if there is no place reserved for it.

Falcikas avatar Jan 14 '21 13:01 Falcikas

I see. Do you have any potential solutions in mind for it?

Em qui., 14 de jan. de 2021 às 10:31, Ernest F. [email protected] escreveu:

Its because of the dynamic DOM element. I think any cloned element added to tree causes CLS if there is no place reserved for it.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/nolimits4web/swiper/issues/4076#issuecomment-760197890, or unsubscribe https://github.com/notifications/unsubscribe-auth/AC6UQSJQH7TT3DNZFMV425LSZ3WZLANCNFSM4VHANYSA .

dpw1 avatar Jan 15 '21 00:01 dpw1

I'm experiencing the same issue with Swiper and loop mode enabled. With loop mode disabled my CLS score is 0.0006 and with loop mode enabled the CLS score is 0.298. Any site that has a CLS score of above 0.1 is classified as "needs improvement" and any above 0.25 as "poor" https://web.dev/cls/. Google has outlined that they will start using CLS which is part of Core Web Vitals https://web.dev/vitals/ for search engine rankings so it's quite an important issue to solve.

lytesaber avatar Jan 18 '21 23:01 lytesaber

As a temporary solution, turning off loop worked fine for me.

maxymczech avatar Jan 19 '21 00:01 maxymczech

Yes, this works as a temp solution however turning off loop isn't really the ideal option. I'm using swiper for promotional banners and product display carousels for e-commerce based sites where having the carousel loop is better UX in my opinion. Once again though it's Google creating additional head aches for web devs to keep up with their "ideal" metrics with the underlying threat of getting penalized in search rankings....

lytesaber avatar Jan 19 '21 00:01 lytesaber

Seems like i found a solution

  1. set duplicates position to absolute
  2. fill their places with ::before/::after pseudo-elems. Use min-width to set width
  3. replace duplicate:last-child to the end of wrapper using 'left'

for example, my slides have full-screen width, and i got 4 slides in a loop and this reduced CLS back to 0, although swiper is still working normal: image

K-ETFreeman avatar Jan 21 '21 14:01 K-ETFreeman

I try and yes, that also get the problem about CLS when using loop, but not only there have CLS when I combine with slidePerView by 3 or more together, that increase my CLS too.

whitersun avatar Jan 27 '21 05:01 whitersun

@K-ETFreeman Unfortunately this solution is breaking the design on my end.

dpw1 avatar Jan 28 '21 11:01 dpw1

The solution doesn't work for me neither because I can have any number of slides from the back-end, I cannot hardcode it as left: 500%, that won't work for 2 slides or 3 slides, only for 4... 🤔

norbertorok92 avatar Jan 28 '21 12:01 norbertorok92

I can confirm that turning off loop fixes my CLS score. It's not a deal breaker for my site so it's fine.

mcometa avatar Feb 08 '21 19:02 mcometa

I'm experiencing the same issue and Core Vitals are needed on my end. I noticed that without loop the CLS turns back to 0, but with Autoplay the slider keeps displaying the first slide when it comes to the end, I'm wondering what loop actually does, though.

ciromattia avatar Feb 09 '21 18:02 ciromattia

@ciromattia loop allows you to manually loop through slides in a "circular" manner (1 - 2 - 3 - 4 - 5 - 1 - ...)

maxymczech avatar Feb 09 '21 19:02 maxymczech

got the same problem, in my situation helped loop: false, and had to change one element in renderBullet function

renderBullet: function (i, c) {
                return '<span class="' + c + '"><span class="swiper-pagination-txt">' + $(this.slides[i+1]).find('.banners-name').html() + '</span></span>';				
            }

changed only one element from i+1 to only i $(this.slides[i])
becouse like was said on this forum, it was generating one more layer which produces CLS. Strange thing is that even when loop is false, my slider is still working properly and looping all images... and no CLS at all :)

adekkpl avatar Feb 16 '21 17:02 adekkpl

I used a similiar @K-ETFreeman solution, in my case I have only one slide per view and full page width. This code works for me, don't increment LCP and content is visible also with javascript disabled (if you managed it before)

#{$slides-wrapper-selector}:not([style*='translate3d']) {
    max-width: 100%;

    #{$duplicated-slide-selector} {
        position: absolute !important;
        left: -9999px;
    }

    &::before {
        content: '';
        min-width: 100%;
        position: relative;
        left: -9999px;
    }

    #{$slide-selector} {
        transform: translateX(-100%);
    }
}

I hope that this workaround can help someone.

AleCiotto avatar Feb 17 '21 08:02 AleCiotto

Hi all, I think it may help to know this useful resource where you can find CLS metric change log https://chromium.googlesource.com/chromium/src/+/master/docs/speed/metrics_changelog/cls.md

I am not sure about your examples but there are more fixes coming with Chrome 89 and 90 soon you can already test in Chrome Canary.

gilbertococchi avatar Feb 22 '21 10:02 gilbertococchi

I'm also seeing this issue and unfortunately turning off loop isn't an option for me. Haven't been able to find any solution that works yet 😞

kristofferdamborg avatar Feb 23 '21 13:02 kristofferdamborg

@kristofferdamborg did you try your site on Chrome Canary 90 to check whether the issue is still happening with loop on?

gilbertococchi avatar Feb 23 '21 13:02 gilbertococchi

Guys, I think it is not really the Swiper issue.

Yes, in loop mode it duplicates and adds slides to DOM, it is necessary and must. But! it doesn't create any visual issues and there is no actual layout shift, and what is more important, there is no UI/UX issues for users.

All mentioned workarounds above can do the trick, but all of them doesn't fix anything except "fix this metric", they don't fix the issue, because there is no issue. Even worse, trying to fix this "metric" with these workarounds can just make user experience worse and break the Swiper.

For me, in this case, issue is in the web-vitals library that treats any, literally, any DOM change as CLS, and adds headache to developers for problem which is not exist

nolimits4web avatar Feb 23 '21 15:02 nolimits4web

@nolimits4web Sorry to chime in, I hope you appreciate the feedback.

I don't know your widget in detail but I can confirm that the CLS metric issue I was able to notice here (that is not related with the web-vitals library but the metric implementation in Chromium) should be fixed with Chrome 90.

The fix on the metric has been already committed in Chromium, you can find all the CLS metric change log here: https://chromium.googlesource.com/chromium/src/+/master/docs/speed/metrics_changelog/cls.md

Hope this helps.

gilbertococchi avatar Feb 23 '21 16:02 gilbertococchi

@gilbertococchi thanks, just checked and indeed it is fixed Chrome 90.

Screenshot (Chrome 88 vs Chrome 90):

screenshot

nolimits4web avatar Feb 23 '21 16:02 nolimits4web

There is my fix for CLS for a full width swiper. Put this CSS before swiper.js initialized:

.swiper-container {
	position: relative !important;
}
.swiper-slide-duplicate:first-child {
	position: absolute !important;
	width: 100% !important; /* seems like you can remove this line but I didn't test without it */
	left: 0 !important;
}
.swiper-wrapper::before {
	content: '' !important;
	min-width: 100% !important;
}

mihaon avatar Apr 20 '21 20:04 mihaon

Yep, I've been looking everywhere to solve my CLS issue. Changed the loop from true to false and the CLS went right down to 0!

Saying that, @m-haritonov CSS solution does exactly the same while the loop is on true. Nice one! 👍

ced--ced avatar May 01 '21 20:05 ced--ced

The CSS solution didn't work for me. But disabling loop and autoplay initially, and enabling them after the page finished loading did:

$(window).load(function(e) {
	if ( $('.swiper-container').length > 0 ) {
		setTimeout(function(){
			const swiper = document.querySelector('.swiper-container').swiper;
			swiper.loop = true;
			swiper.autoplay.start()
		}, 3000);
	}
});

thaissa avatar May 05 '21 17:05 thaissa