#TECH

Controlling Lottie animation through gestures

As a developer, I have always been smitten by the beautiful animation concepts which designers put on their Dribbble accounts for mobile apps and websites.

Until now, I used to spend my days and nights scratching my head in order to build those animations in my projects. That’s because designing animations that delight users is not an easy task. When users are interacting with these animations, we have to make sure that these animations elicit the right emotions. Such as the joy of completing a task successfully communicated by an animation. I wanted to build those experiences for our users.

But developing such experiences requires time, effort and for each animation one has to take a different coding approach. One might also need to spend a considerable amount of time developing these animations.

However, recently I found out that there is an approach where one can save a lot of time in creating beautiful experiences. And it also gives the users full control of the animations they are interacting with.

What’s exciting is that you don’t have to be an expert in mobile development, or have a deep knowledge of any of the over-complicated Animation frameworks.

I tried it myself and loved it. That’s why I thought I’ll share it with all of you. Let’s get started.

In my toolset, I have chosen the most generic of the platforms to start with.

  1. React Native. (Even basic knowledge of Javascript would suffice).
  2. A Lottie animation file exported by using Body movin  plugin from Adobe After effects.

That’s all you need. The below is a final animation that we are going to build.

Final Animation

As you can see here, the user is able to control the animation progress with his Swipe gesture. This is simply beyond the capabilities of heavily loaded GIFs where they just play in meaningless loops.

So let’s get started.

First of all, we start by creating our project and installing some must have libraries in the app.

  1. Lottie
  2. React native viewpager

For your reference, here’s how my folder structure looks like-

Folder structure

Project Structure
Project Structure

 

Dependencies

Project dependencies
Project dependencies

 

Animation

The video above is simply a result of Lottie .json file which I have used in my code. Just Drag and drop the .json file and provide it as a source to your <LottieView /> component.

The concept I have used while making these animations is simple math. I have designed these animations in a way that the first 3 seconds of the animation corresponds to the girl transforming from fat to thin and the last second of the animation corresponds to swaying away from the page.

So, first of all we are going to achieve how to show animation in the View pager.

In the Onboarding.js,

export default class Onboarding extends Component {
  constructor(props) {
    super(props);

    const loadingText =
      'In this phase, you will have high-calorie food in order to accelerate your metabolism';
    const losingText =
      'This is a very low calorie phase lasting 6-8 weeks. You will experience rapid, healthy weight loss without hunger or cravings.';
    const resetText =
      'In this phase you will increase your calorie intake keeping the food composition similar as of the last phase. The purpose of this is to restore your metabolism to its normal level and';

    this.OBData = [
      {
        title: 'Loading Phase',
        desc: loadingText,
        animation: Animation.OB1,
      },
      {
        title: 'Losing Phase',
        desc: losingText,
        animation: Animation.OB2,
      },
      {
        title: 'Reset Phase',
        desc: resetText,
        animation: Animation.OB3,
      },
    ];
  }

  render() {
    return (
      <ViewPager
        style={[style.pagerStyle]}
        onPageScroll={event => {
          console.log(event.nativeEvent);
        }}
        onPageSelected={event => {
          console.log('Page selected', event.nativeEvent);
        }}
        showPageIndicator>
        <OnboardingComponent data={this.OBData[0]} />
        <OnboardingComponent data={this.OBData[1]} />
        <OnboardingComponent data={this.OBData[2]} />
      </ViewPager>
    );
  }
}

const style = StyleSheet.create({
  containerStyle: {},

  pagerStyle: {
    width: '100%',
    height: 550,
  },
  pageStyle: {},
});

Onboarding.js (Snippet 1)

We define our data model which we are going to show on each page. Each page of the pager is a component <OnboardingContainer /> which holds your LottieView and a couple of texts.

return (
      <View style={[style.containerStyle]}>
        <View style={[style.lottieStyle]}>
          <LottieView source={animation} autoPlay loop />
        </View>
        <Text style={style.walkThroughHeadingTextStyle}>{title}</Text>

        <Text style={[style.walkThroughSubTextStyle]}>
          {desc} {this.props.children}
        </Text>
      </View>
    );

OnboardingContainer.js (Snippet 2)

Using this code, we have achieved what we have in the above video. Nothing special, simple enough.

Let’s start coding for the part where we will control our animation through Swipe gesture.

What do we need? 

  • When we scroll to a new page, animation should start playing.
  • For the first two pages, animation should be played for 75% of their total duration and last page should play the total animation.
  • Animation should reset to 0 when it is not visible otherwise it will show the final part of animation( the last 1 second) before starting over, when we scroll back to a previous page.
  • For the first two pages, after initial animation, animation should be controlled by user swipe.

In order to implement these 4 steps, we need to pass a few props to our OnboardingComponent.js

  • play :- whether to play animation or not. (Based on selected page)
  • offset:- to control animation when user swipe
  • finalOffset:- final portion till which animation will autoplay.
<OnboardingComponent
  data={this.OBData[0]}
  play={this.state.selectedPage === 0}
  offset={this.state.selectedPage === 0 ? this.state.offset : 0}
  finalOffset={0.75}
/>

Onboarding Props (Snippet 3)

Now we need to implement ViewPager callback to set our `selectedPage` and `offset`. <ViewPager /> provides `onPageSelected` and `onPageScroll`.

If the page is scrolled from left to right, we will have negative offset value and we don’t want our animation to roll back when we are sliding back, or previous screen animation to play. Therefore, we are checking for negative value. Rest is pretty straight forward.

<ViewPager
style={[style.pagerStyle]}
onPageScroll={event => {
  const {offset} = event.nativeEvent;

  if (offset < 0) {
    return;
  }

  this.setState({offset: offset});
}}
onPageSelected={event => {
  const page = event.nativeEvent.position;
  this.setState({selectedPage: page});
}}
showPageIndicator>

View Pager callbacks (Snippet 4)

That’s it, we are done with Onboarding.js

Let’s implement our OnboardingComponent.js . We will start by defining our constructor.

constructor(props) {
    super(props);

    this.state = {
      ...props,
      isAnimating: props.play,
    };

    this.progress = new Animated.Value(0);

    if (props.play) {
      this.startAnimation();
    }
  }

OnboardingComponent.js (Snippet 5)

Here I am using `isAnimating` state variable to detect if our animation is auto-playing or not and also passing `play`, `offset` and `finalOffset` from props. In addition to this, `this.progress` controls our autoplay portion of the animation. Once we land on a new page, we will start an Animated.timing() function for 3 seconds till our `finalOffset` (from props). Once the autoplay animation is over, we will set `isAnimating` to false.

As we need to update our state on swipe of ViewPager in the parent component, we will have to use this particular life cycle method.

static getDerivedStateFromProps(props, state) {
    if (state.play === props.play && state.offset === props.offset) {
      return state;
    }

    if (state.play === props.play && state.offset !== props.offset) {
      return {...state, offset: props.offset};
    }

    return {
      ...props,
      isAnimating: true,
    };
  }

getDerivedStateFromProps (Snippet 6)

First, we are checking if anything in props has changed or not. If nothing has changed then we don’t need to update our state.

In the next condition we are checking if the user is swiping the page. Check snippet 3, we are only updating `offset` if the page is currentlySelected page otherwise it is 0.

Finally we are returning in `initialState` if nothing matches.

Once the `play` prop is changed to true, we will start our animation.

componentDidUpdate(newProps) {
  if (this.props.play === newProps.play) {
    return;
  }

  if(newProps.play){
    this.startAnimation();
  }
}

Starting animation if prop update ( Snippet 7)

In the <LottieView /> we are controlling our animation using `progress` props. If `isAnimating` is true, we are setting `progress` to animated value otherwise we are deciding animation `offset` based on user swipe.

const {animation, title, desc} = this.state.data;
const {isAnimating, play} = this.state;

let offSet = 0;

if (!play) {
  offSet = 0;
  this.progress.setValue(0);
} else {
  offSet = 0.75 + this.state.offset / 4;
}

Setting Offset (Snippet 8)

And the final piece of animation is setting the `progress` in the <LottieView/>.

<LottieView
  source={animation}
  progress={isAnimating ? this.progress : offSet}
/>

 

and Voila!!! That’s done folks.

You have your own user controlled Walkthrough page within like 10 mins or so.

Let me know your thoughts on this in the comments.

I have been working on another interesting animation. I will be sharing that too in the next part of this series.

Github link for the project :  https://github.com/NitishQuovantis/AnimatingOnboarding

 

You might also like