Making an animated tab bar with React Native

Simon Trény
BeTomorrow
Published in
5 min readMar 24, 2020

--

Micro-interactions are a key element to make your app stand out and to make users want to interact with it. Tab bars are good candidates for micro-interactions as they are one of the most used components of an app, which ensures that the users will play with it, giving them an immediate sense of quality.

In this post, we will see how to create the “Flashy tab-bar” designed by Cuberto. Cuberto already implemented it for iOS and for Android. Making it with React Native is an interesting exercise as it will lead us to play with hooks, transforms and clipping views creatively.

The “Flashy Tab Bar”, courtesy of Cuberto

The skeleton of our tab bar

To achieve our goal, we will need to create two components, the TabBar itself and the TabItem made of an icon, a label and a dot-indicator. When the item is inactive, only the icon is displayed; when it is active, the icon is hidden and the label and dot-indicators are visible.

First, let’s start with the non-animated TabItem component:

And here is how they look:

Our static TabItem: active at left, inactive at right

Now let’s create the TabBar component. It displays all the items in a row and stores the index of the active item in its state, updating it each time one is pressed:

And here is how it looks in action:

A pretty basic tab bar for now

From basic to classic 🤓

Now that we have created our non-animated tab bar, it is time to decompose the animation we want to achieve. Here is what happens when an item goes from inactive to active:

  • The dot indicator appears by scaling up from 0% to its normal size
  • The label appears with some kind of diagonal transition
  • The icon disappears in a similar fashion
  • The icon and label are translated vertically at different speeds

The easiest parts to implement are the dot indicator scaling and the icon/label translations, so let’s starts with these. For this, we will use React Native Animated and a single Animated value to drive all of our animations. This value will go from 0 to 1 when the item goes from inactive to active. For a more natural animation, we will use spring-based animations.

First, let’s introduce the useSpring() hook that will make it easier to start/stop our animation when the active prop changes:

Here, we use useEffect() to start/stop the Animated.spring animation when the to value changes. The API of this hook is inspired by react-spring (which unfortunately does not support running animations on the native thread at the moment).

Let’s use this new hook to animate the scale and translations of TabItem’s subviews. We will compute scale/translations values by interpolating them from the animated value returned by useSpring(). For now, we will replace icon and label’s transitions by a simple fade-in/fade-out.

Note also that we also removed the active && conditions that were used to show/hide subviews. We did this to be able to animate the disappearing views as you cannot animate unmounted components.

Let’s see how the first iteration of our animated tab bar looks like:

That’s definitely better, but it looks quite classic. As a user, I don’t want to play with it as much as I want to play with Cuberto’s tab bar. Let’s see if we can change this.

From classic to epic 🤩

What makes the Cuberto’s tab bar really stand out is the way views appear/disappear. They don’t fade-in/fade-out but they appear by revealing themselves with a diagonal-shaped transition. There are several ways to achieve this effect with React Native:

  • We could mask the views with an animated mask using react-native-masked-view but unfortunately, this library does not support animated masks on Android yet.
  • We could clip the views with a slightly rotated parent with overflow: "hidden" and use vertical translations to reveal/hide them but this could get tricky as we would need to apply the inverse transform on the child views to keep them from moving.
  • We could also cover the views with an opaque white view that is translated vertically from bottom to top and slightly skewed to give it a diagonal shape. This works well, the only downside with this technique is that it cannot be used on a translucent tab bar (which is not the case here).

In this post, we will use the latter solution as it is the easiest to implement and it works well across all platforms.

In order to do that, let’s introduce a new DiagonalTransition component that makes its content appear/disappear by covering it with diagonal-shaped cover view. The position of cover view is controlled by the visibility prop that goes from 0 to 1.

To have a better understanding of what this component does, here is a look at the component in 3 different configurations:

  • At left, with overflow: "visible" and a "red" cover
  • In middle, with overflow: "hidden" and a "red" cover
  • At right, with overflow: "hidden" and a "white" cover
The DiagonalTransition component, aka the GuillotineComponent 🇫🇷

Putting it all together

Now that we have our new DiagonalTransition component, let’s replace the opacity transition with it. It is just a matter of wrapping the icon and label of TabItem with a DiagonalTransition:

And let’s run it:

That looks a lot better now 🤩

And voilà! The tab bar we made is not that far off from Cuberto’s initial design. We could tweak the animation a bit but the result already looks pretty convincing to me. It also runs 60fps even on low-end Android devices thanks to the use of Animated useNativeDriver property.

The same technique can be applied to implement all kinds of transitions, using different shapes or different transforms (you can replace the translation of the cover view by a 90° rotation for example).

I hope you enjoyed this article! Please share your comments with us in the section below👇

BeTomorrow is a consulting, design and software agency. Click here to have a look at what we do!

--

--