Infinite carousel with framer-motion
The Problem
While creating the app OneJournal, I wanted to provide swipe functionality for navigating between journal entries. This, however, posed a problem because the list of journal entries is infinite. I was using Framer Motion for other animations at the time and decided to create a carousel with this instead of using another library which would potentially increase the bundle size by more than implementing it myself.
Framer Motion provides a convenient set of primitives to build complex animations and by leveraging these primitives we can achieve an infinite carousel with very little code. The article is split up into three sections where we will gradually create this carousel.
- Defining the interface for our carousel
- Positioning the elements on the screen
- Adding drag behaviour.
If you are just interested in the final result, you can scroll to the bottom of the page to find the link to Codesandbox and Github.
VirtualizedPage
Interface
We will need to dynamically render the children based on the currently visible index and also the page to the left and right of this index. For this, we can use render props and pass along the index we want it to display. This is very similar to how React-window does it.
Now that we know what the interface will look like we can start thinking about creating the components. We will separate the problem into two different components.
One will be responsible for orchestrating everything and another one for rendering the child pages.
Positioning the different items in the carousel
Now that we have the basics laid out and the API is defined we can start working on positioning the different items or pages in the carousel.
For our simple use case, we will lay the pages right next to each other. This can be done by defining left and right on the page based on the index. Because overflowX
is set to hidden
on the parent, the page to the left and right will not be visible until we start dragging.
style={{
...pageStyle,
left: `${index * 100}%`,
right: `${index * 100}%`
}}
Add drag behaviour
To add the drag behaviour, we will need to keep all the items in sync with each other. Luckily framer-motion provides a hook to do just that calleduseMotionValue
.
We now have something where you can drag the items but we still have a few problems to solve.
- The carousel only displays items with an index
-1, 0, 1
and never updates. - The carousel does not snap to the item when letting go
To achieve both of these requirements, we will need to update the current index and animate x
to the correct value. For simplicity’s sake, we will update the index when the user drags the item more than 1/4 of the width of the container.
Wrapping up
By using the primitives provided by framer motion we can easily create our own version of a reusable carousel. This component is used for the journal overview, the calendar navigation and the image gallery inside the app.
You can find the final result here:
https://codesandbox.io/s/github/koenvg/infinite-carousel-with-framer-motion
https://github.com/koenvg/infinite-carousel-with-framer-motion