Introducing Sideswipe: a cross-platform carousel for React Native

kurtiskemple
Exposition
Published in
5 min readFeb 12, 2018

--

Recently I found myself in need of a flexible carousel solution that could support some pretty tough requirements, mainly:

  • Support an infinite number of items
  • Feel identical on iOS and Android
  • Support snapping to nearest index
  • Account for force (need to be able to move many items in one gesture)
  • Be flexible enough to accommodate custom animations
  • Have an easy-to-use API

Round 1

My first attempt was to utilize open-source and use something someone else had already created. So I set out on my Google journey, and while I was brought to so many amazing places, nothing felt quite like home.

Here are some of the really cool projects I found along the way, many of which were inspirational in the creation of react-native-sideswipe:

While all of these solutions are quite amazing, they don’t meet all the requirements stated above. Any other solution I found either seemed to be experimental or still not have all of what I needed. Not to mention most solutions had largely different solutions for iOS and Android. This means you need to understand two distinct implementations to debug issues or try to extend the library.

Round 2

Having failed to find a solution I figured I would try a first run at creating my own. I knew that to support large datasets, anything I built HAD to be based on FlatList. FlatList is an extended ScrollView, so my first attempt was connecting to onScrollBeginDrag, onScrollEndDrag, and onMomentumScrollEnd handlers to control final positioning of the carousel. It looked something like this:

Using scroll handlers to control carousel index

This solution got me about 70% of what I needed but there were a few issues that made it unusable for the final product:

  1. It wasn’t snappy: it would free scroll until the end of the scroll event and then snap to the closest index.
  2. The scroll/momentum end handlers get called at waaaay different times on each platform. Using it on an iOS and Android device felt like using two different libraries.
  3. On Android you have zero control on deceleration rate, which meant end events weren’t called until scrolling was almost complete which created a poor user experience.

Round 3

Now knowing that trying to use the actual scroll events to manage carousel positioning was not going to work, I decided to do the most dreaded thing in all of UI development and hijack the native behavior.

Hijacking swiping gone terribly wrong

I figured that if I could move the scrollview 1:1 with the user’s finger and then depending on how fast they are dragging (velocity), compute the proper index to land on, I could then scroll the list view programmatically.

I was able to do this by disabling scrolling on the FlatList and then using PanResponder to control the list.

Using PanResponder to control FlatList scroll position

This ended up working surprising well! At first the code was a bit of a mess but I got some great feedback from Jason Brown and Narendra N Shetty and was able to land on a solid solution! While it was close to meeting all the requirements, I had yet to deal with animations for the carousel items in an acceptable way.

Round 4

The first solution was a second component that was passed the index of the item and the current index. This component provided an animated value that your component could use. This was a less than ideal solution because the index had to update before your components would animate. Ideally we want to animate based on scroll position, not index changes, which would provide a much smoother effect.

Old solution for handling transitions between items

I knew I could add an Animated.event to the FlatList and pass the animated value to the child components but the value would be the raw scroll position. This means the components using the animated value would have do some math to figure out if they were the active index. This is a less than ideal user experience.

In order to solve this I made use of Animated's divide function. This function takes two animated values and returns a new animated value that is the difference of the first and second animation. It also updates any time either one of the underlying animated values updates.

What this means is we can divide the scroll position by the width of a child item (which is a required prop) and that will give us an animated value starting at 0 and ending at the length of the carousel. Now child components can animate based on their index instead of scroll position but the animation will still be based on scroll position under the hood. Here’s what that looks like in code:

Using Animated.divide to get animated value for carousel items

Now each carousel item gets an animated value they can use to create entrance and exit animations based on their index in the list, not the scroll position of the underlying FlatList, this provides a much better user experience:

Carousel item using animated value to add enter/exit animations, see example below

Wrapping Up

Once animations were complete I wanted to really test the implementation on both platforms and build a few examples to showcase what the outcome of this coding adventure could do.

Using Expo’s React Native playground, snack, I was able to quickly test out my implementation on real devices as well as create some awesome examples that I can easily share. Now anyone can try it out for themselves! 👏 👏 👏

#SPACEDChallenge Snack
Pokedex Snack

If you haven’t tried snack out yet, I highly suggest giving it a few minutes of your time. There are a bunch of awesome examples you can check out at expo.io or you can just dive right in at snack.expo.io!

I really look forward to seeing what others create with react-native-sideswipe and I hope to see some snacks in the comments section below!

Curious? Follow our awesome guest blogger Kurt Kemple on Medium, Github and Twitter and check out what he’s working on in the React / React Native / GraphQL eco-system

Want to check out the source code in full? You can find the project on GitHub.

If you enjoyed this article don’t forget to drop a few 👏 and a share on social media is always appreciated! 🙌

--

--

Web / Mobile / GraphQL enthusiast 🙌 Co-organizer of @NYCGraphQL 🗓 Technical Writer 🖋 Mentor 🎓 Fine Dancer🕺