Infinite loop Scrolling Image Slider with buttons for navigation
How to create an infinite card/image slider that has a a smooth and uninterrupted slide that loops. Once a user interacts with slider, it should stop sliding and from then on only able to navigate by using prev/next buttons or drag-able on mobile.
Should work like this https://codesandbox.io/s/embla-carousel-github-issue-105-i2gji?file=/src/js/EmblaCarousel.js or the gif bellow (with buttons for navigation)
https://user-images.githubusercontent.com/273716/93586286-5e4abe00-f9a8-11ea-936b-6b09393e631a.gif
davidjerleke
CodeSandbox
Embla Carousel - Github Issue 105 - CodeSandbox
React version of Embla Carousel.
6 Replies
It's late for me and I'm not going to do it right now but I can say the methods I might use and you can try it out. If you're stuck on how to achieve it
Things that I have recognised you want (just to be clear and if I've missed anything)
- Infinite scroll, and should support going left (things are easier if you can settle with infinite going right)
- Scrolls automatically and stops after being hovered
- Able to click/tap and drag to move while supporting links inside, and clicking those doesn't affect scrolling
- The link you shared is not infinite and doesn't scroll by itself
Method
HTML layout
Using IntersectionObserver we can identify whether a child element is visible or not.
The idea is that you have children and watch for the cards on the ends and if a child on the end is visible you move one from the other side.
You can use absolute positioning or not use absolute positioning. Absolute positioning is a little simpler but there's the drawback of it being more difficult to be responsive as you can't get the heights of the children
Intersection Observer API - Web APIs | MDN
The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
Method : Absolute
div
will be position: relative
with section
s being position: absolute
.
When clicking and dragging you are moving the div
and when you scroll such that one of the ends becomes visible you want to grab the element on the other side.
If scrolling right you are bringing an element from the left to the right. You want to get the bounding box of the rightmost child and then set left
of the moved child to left + width + gap
If scrolling left you are bringing an element from the right to the left. You want to get the bounding box of the leftmost child and moved child. You want to set left
of the moved child to left of leftmost child - gap - width
Method : Non-Absolute
div
will be position: relative
and display: flex
.
This method involves faking the position of the div
to look like it's infinitely scrolling. You will be scrolling div
but then shifting left
when elements move, you never have a left
greater than 0px
and never less than the width of div
- 100vw
.
If scrolling right you are bringing an element from the left to the right. Get the bounding box of the moved element, when you move the element also add the width to div
.
If scrolling left you are bringing an element from the right to the left. Get the bounding box of the moved element, when you move the element also subtract the width to div
.
Dragging
You want to watch for the mousedown
& touchstart
(carousel), mouseup
& touchend
(document), mouseleave
& touchcancel
(document), and mousemove
& touchmove
(document) events. I will only talk about mouse
from now on, but just know to do the same with the paired touch
. When mousedown
add the event listener for mousemove
to the document, then when mouseup
or mouseleave
remove the event listener.
You can either track the difference between the current x
to the previous and then update (you will need to get this anyway), or you can track the x
from mousedown
and then subtract that from x
from mousemove
.
For now just store the current x
position that carousel should have, it will be used later.
You will also want to track how fast the movement is if you want the carousel to continue spinning if you click, drag, and release while dragging. This is trickier than it seems because of the case where someone moves right and then at the last moment move left. The difference in x
between each mousemove
event is small, so you will need to store a few of the dx
values as well as the difference in time. Then when it comes time to calculate the velocity you can add up the dx
and the times and divide. You should work backwards and if values change from positive to negative or negative to positive stop looking, you just want the last direction. You also don't want to track too many points as it may make the movement sluggish. You only need to calculate the velocity when dragging stops, don't do it every mousemove
. Play around and find a value that works.
Movement
mousemove
causes a lot of updates and it's unnecessary to update the DOM for every event, so the actual movement will be handled when the browser says that it can. We can do that requestAnimationFrame. We will have a function that will tick every animation frame (if you want to be fancy you can stop and start this when it's still and when moving. When the velocity is 0
and not currently dragging you can just not call requestAnimationFrame
and when you mousedown
or touchstart
you can start it again).
Inside this function update the left
of div
to x
Physics
To have the carousel continue after releasing it we will use velocity. This velocity will continue to be added to x
but at a diminishing rate.
Physics should be handled inside the function for movement and only be active when not dragging. On each frame multiply velocity with a number like 0.99
(closer to 1
and it glides move, closer to 0
and it stops sooner. Just be aware that this number is multiplied a lot and so numbers are going to be fairly close to 1
even for something that feels like it has high friction).
If you need further help and nobody else helps, I can help tomorrowIt just needs some polish and set to move by default but I ran out of time. I started a bit late and only gave it 2 hours
Fixes:
- ✅ getting velocity needs to compare to current time, dragging, stopping, and releasing shouldn't result in movement
- ✅ velocity seems a little inconsistent
- ✅ move by default
- touch doesn't work
- breaks if you move it too far too fast
It works exactly as I had described
This is something that would require a couple days to get it perfect, and I've given it 4 hours. I'm going to be indisposed tomorrow and for a few days after. But this is one way to do it for when you make one yourself. Not allowing user interaction would make it far easier, just adding buttons would make it far easier, not having it be infinite would be a little easier.
Hello Z, thank your very much for the help. I’ve spent my whole weekend on this, but unfortunately didn’t get that far.
Your version looks cool, and am sure no one will scroll that much so it breaks 🤞🏿 i’ve unfortunately started my 9-5 and won’t be able to spend any time on this. Will have a glance at this, next weekend. If you happens to finish this before, could you ping me?
I have no idea how long I'm going to be unable to work for, but this is something I want to polish, it's a lot of fun to work with