Embla-carousel: Blank Carousel when looping with 2 slides (inactive browser tab)

Created on 23 Oct 2019  路  9Comments  路  Source: davidcetinkaya/embla-carousel

Hello,

when trying to create an automated slideshow with two slides and loop: true, I stumbled upon an interesting edge case. I used setInterval() to call scrollNext() every few seconds. After I switched tabs and came back later (one or two minutes is enough), the embla carousel was blank and the source code had a weird transform value:

<div class="embla__container" style="transform: translate3d(-5.72608e+10%, 0px, 0px);">

It can be reproduced here: https://codesandbox.io/s/embla-carousel-align-start-vllyp

I think this only happens in quite specific conditions - e.g. I was only able to reproduce this with two slides. I worked around the problem by using the Page Visibility API and only calling scrollNext() if the page is visible, but I thought it might be worth reporting the issue.

demonstration documentation

All 9 comments

Hi Gerhard (@niubsta),

Thank you for taking time to open this issue. I'm very happy that you always provide a helpful CodeSandbox demonstration 馃槂.

Interesting. I don't know exactly why, but something weird definitely happens when setInterval() is used and the tab looses focus. Maybe it's similar to this issue on StackOverflow, but I don't know. How browsers handle setInterval() could have changed since then. Embla uses requestAnimationFrame() internally to render visual updates which only executes on visible/focused tabs. Maybe the setInterval() function is clashing with that and is trying to force update the blurred tab or something. But it's strange if it only happens with two slides. If you find anything in the code that could be related to this issue, please inform me.

Would you mind trying to reproduce the issue in this CodeSandbox?

馃憜 The CodeSandbox above is a fork of my Autoplay demonstration which demonstrates one way to implement autoplay for Embla Carousel. It uses recursive setTimeouts() wrapped in requestAnimationFrame() which prevents autoplay from running when the browser tab is blurred.

Kindly,
David

Hello @davidcetinkaya ,

thank you for your answer, I really appreciate your quick and detailed feedback. Sorry, I must have missed the autoplay demonstration - can't reproduce the issue there.

Actually replacing the embla.scrollNext(); in the setInterval() with this code from your example ...

if (embla.selectedScrollSnap() === lastIndex) {
  embla.scrollTo(0);
} else {
  embla.scrollNext();
}

... fixed the issue in my code too. So this version here works flawlessly: https://codesandbox.io/s/embla-carousel-align-start-pllz2

I'm not too familiar with the code but with a little bit of testing I discovered that, as soon as you change the tab, the value of targetVector in the method byIndex() gets higher and higher, whereas if the tab is active, it constantly switches between 0 and -100. So I guess that's where the problem arises. Don't know if that information is helpful?

There's probably no need to put anymore effort into this as my approach of just using scrollNext() might have been a bit naive. On the other hand, maybe it would make sense if the scrollNext() method actually automatically calls scrollTo(0) when the end of the carousel is reached (and loop = true)?

Hi Gerhard (@niubsta),

No worries! Actually the autoplay demonstration is not so easy to find, you can find it on my CodeSandbox profile page. I've been wanting to add a CodeSandbox section with inspiration links in the README like the embla-carousel-react package but simply haven't had much time lately.

With a little bit of testing I discovered that, as soon as you change the tab, the value of targetVector in the method byIndex() gets higher and higher, whereas if the tab is active, it constantly switches between 0 and -100. So I guess that's where the problem arises. Don't know if that information is helpful?

Thank you very much, it helps a lot 馃憤. I will take a look at this as soon as possible.

using scrollNext() might have been a bit naive.

Actually I don't think it's naive. It's kind of strange because when the carousel is set to { loop: true } the scrollNext() function works just fine when the browser tab is focused. Embla subtracts/adds (depending on the scroll direction) the scrollable length from/to the targetVector when the carousel has reached its scrollable limit and starts over - hence the loop effect. But somehow this fails with scrollNext() on a blurred tab with two slides.

I'll let you know how this goes 馃檪.

Side note: I must say that I'm impressed with how you find these edge cases. There's no money in this project because it's open source, but if it was I would have hired you as a tester 馃槄. Nonetheless I'd like to show my appreciation so I wonder if it's ok to add you as a contributor here?

Awesome, thank you for digging into this! And yes, it IS weird as using scrollNext() also works on a blurred tab if there are three or more slides. So, it's definitely an edge case.

Glad I could help! I've tried quite a few carousel scripts recently and liked Embla carousel the most. So sure, I'd be happy if you added me to the contributors, even if I just did a bit of bug-hunting so far ... :-)

Hello Gerhard (@niubsta),

I've done some digging and I know why this is happening. My initial hunch was kinda right, the setInterval() timer is clashing with requestAnimationFrame().

Why this is happening at all

Basically, what's happening is that as soon as the browser tab is blurred, Embla stops doing visual updates because requestAnimationFrame() is designed to not run on an inactive browser tab. This makes sense because it saves processing power and battery by preventing expensive rendering from taking place when a tab is blurred. BUT, as you can see in the screenshot below, setInterval() is running even though the tab is inactive and calling scrollNext() repeatedly.

Embla resets the targetVector inside the visual render loop, and it must happen here in order to create the seamless infinite effect. But as already mentioned, the render loop is paused when the browser tab is blurred which means that nothing is stopping targetVector from growing out of hand when scrollNext() is called by setInterval().

console

Why this is happening with 2 slides only

Embla uses a specific approach to cover an edge case when the carousel only has 2 slides. When it has more than 2 slides, both scrollPrev() and scrollNext() will find the targetVector (to which position the carousel should scroll) by looking for the shortest route possible. scrollNext() will in this case always move forward because the shortest route from i.e. slide index 0 to 1 is forward. This is because a backward movement to index 1 would have to scroll by all slides from the last index down to 1, causing this route to be longer. BUT, when the carousel only has 2 slides, the shortest route from slide index 0 to 1 could actually be backward, even though scrollNext() is called!

This can be confusing because scrollNext() is expected to always move forward, and scrollPrev() backward. This is why Embla only adds to the targetVector when it has 2 slides. In contrast, when it has more than 2 slides, it will compare all possible routes and choose the smallest possible targetVector which prevents targetVector from growing out of hand.

Conclusion

I hope I've managed to explain why this happens 馃槄. I'm gonna argue that this isn't a bug. Maybe we should ask the question: Why should we run autoplay for a carousel when the browser tab is blurred? But the main reason is that at the time of writing autoplay isn't a native feature of Embla Carousel and I'm going to leave the implementation up to the user. There's a couple of ways to avoid the issue you describe. Below are two different ways:

I hope that this makes sense? Anyways, I'm very happy for your contributions and clear demonstrations! Thank you very much for reporting this and don't hesitate to do it again. I've added you as a contributor 馃帀.

Best,
David

Hello @davidcetinkaya ,

first of all - kudos for the extraordinarily detailed explanation! :-)

I was a bit confused when you explained that when using scrollNext() with two slides the animation actually goes backwards. This isn't the case in the example code in my initial post. The animation always goes forward, which is because of loop: true.

So may I ask: Is there a reason why you use loop: false in your AutoPlay code and then

if (embla.selectedScrollSnap() === lastIndex) {
  embla.scrollTo(0);
} else {
  embla.scrollNext();
}

instead of just using scrollNext() with loop: true?

Considering the issue itself: Your solution with using requestAnimationFrame is without a doubt a much cleaner approach than being sloppy and just using setInterval() like I did in the first place.

So I don't think it is necessary to actually try to fix this issue. However, I would suggest adding a CodeSandbox section as you've mentioned before, to make sure everyone interested in using Embla carousel for a slideshow gets pointed in the right direction concerning a proper implementation.

Does this make sense to you?

Hi Gerhard (@niubsta),

I was a bit confused when you explained that when using scrollNext() with two slides the animation actually goes backwards. This isn't the case in the example code in my initial post. The animation always goes forward, which is because of loop: true.

I'm sorry for being unclear. I'm was actually referring to how Embla internally decides which route to take to the target slide, not your demonstration link. Because this edge case is already taken care of so scrollNext() already behaves as expected. I think it my attempt to explain why the issue with how the growing targetVector occurs when the carousel has 2 slides.

So may I ask: Is there a reason why you use loop: false in your AutoPlay code and then

if (embla.selectedScrollSnap() === lastIndex) {
  embla.scrollTo(0);
} else {
  embla.scrollNext();
}

instead of just using scrollNext() with loop: true?

Of course, good question! When issue #2 was opened I created this autoplay CodeSandbox to demonstrate how to achieve autoplay with Embla. The simple truth is that I just went with loop: false without thinking about it much. And the reason for the code you mention is that when loop: false and Embla has reached the end slide, scrollNext() won't do anything. As described in the API documentation:

  • scrollNext() - Scrolls to next snap point if possible. If loop: false and the carousel is on the last snap point this method will do nothing.

So I wanted autoplay to rewind back to the first slide when the carousel had reached the end slide. You're right about the fact that if the carousel has loop: true you can just go with scrollNext() instead of the if else statement. And I should update the autoplay CodeSandbox demonstration to cover both loop: false and loop: true cases.

However, I would suggest adding a CodeSandbox section as you've mentioned before, to make sure everyone interested in using Embla carousel for a slideshow gets pointed in the right direction concerning a proper implementation.

I think this makes a lot of sense and I will add the section with links as soon as possible 馃檪! If you have created any other CodeSanboxes with Embla that you think could inspire others, feel free to send the link and I'll add it to the list 馃憤.

Thank you!
David

Hello @davidcetinkaya ,

gotcha, thank you for the explanation! Of course I'll let you know when I create some CodeSandbox demonstrations that might be useful for others. At the moment I'm using Embla carousel mainly for rather basic usecases like the slideshow. :-)

One again, thank you for all your efforts!

Kind regards,
Gerhard

Hello Gerhard (@niubsta),

I've updated the Autoplay CodeSandbox Demonstration so it works with both loop: false and loop: true 馃憤. I've also added CodeSandbox links to the README here.

Thank you very much for improving Embla Carousel 馃檪!

Best,
David

Was this page helpful?
0 / 5 - 0 ratings

Related issues

EctordAniel picture EctordAniel  路  3Comments

S1SYPHOS picture S1SYPHOS  路  6Comments

patrickhuang94 picture patrickhuang94  路  3Comments

th-km picture th-km  路  5Comments

iamchanii picture iamchanii  路  5Comments