Add Spring Animations to your NativeScript App

You've seen apps with really cool spring animations. You've heard of developers talking about Framer Motion or Remotion for React Native…

Take control of your career. Build JavaScript mobile apps.

You've seen apps with really cool spring animations. You've heard of developers talking about Framer Motion or Remotion for React Native. What if I told you that you can create a similar animation in NativeScript?

Let me show you how you can create spring animations in NativeScript using a Javascript library called Popmotion. This is the library that powers Framer Motion!

demo

I showcased this type of animation in one of the episodes on my Movies Speed Code Series on Youtube. You can check out the episode below 👇





For those that prefer going through the source code, check out the repo here

Let's get started

I'll be using the Angular flavor of NativeScript for this blog post. However, since Popmotion is a Javascript library, you should be able to use it with the other frameworks with a few tweaks.

Let's start by adding the Popmotion dependency

npm i --save popmotion

Let's create a simple layout that we can animate using Popmotion

<GridLayout rows="*" columns="*">
  <StackLayout #element margin="10" borderWidth="2" borderColor="#000">
    <Label text="I'm a label"></Label>
  </StackLayout>
</GridLayout>

Accessing the Element

In your component file, access the layout using its id and the ViewChild decorator:

import { ElementRef, ViewChild } from '@angular/core';

...
export class YourComponent {

    @ViewChild('#element') element: ElementRef;

    ...
}

When using other flavors (e.g. Vue, React, etc.), getting the reference to the elements from your template would be different, but the sections to follow should be identical.

Animate!

Before we get into the details of the implementation, let's take a step back and go over Popmotion's anatomy and how it works first.

At its core, Popmotion takes a from and a to parameter which then gets emitted periodically on an onUpdate callback. Below is a basic example of this 👇

import { animate } from "popmotion";

animate({
  from: 0,
  to: 100,
  onUpdate: (latest) => console.log(latest),
});

You can use numbers, strings, colors, etc. for the from and to values and Popmotion will manipulate that value, emitting the value of each step in the onUpdate callback until it reaches the final to value. The onUpdate callback is where you would define your UI element's style updates.

Let's see what animating an element's opacity from 0 to 1 looks like using Popmotion 👇

import { animate } from "popmotion";

animate({
  from: 0,
  opacity: 1,
  onUpdate: (latest) => {
    this.element.nativeElement.opacity = latest;
  },
});

You must be thinking that you could easily do this using a regular timer via a setInterval 🧐.

This is what makes Popmotion interesting. It lets you pass in different properties that would modify the `onUpdate callback resulting in different types of animation.

To create a spring effect, you could pass in a type of spring to the animate function.

import { animate } from "popmotion";

animate({
  from: 0,
  to: 100,
  type: "spring",
  onUpdate: (latest) => {},
});

To create a decay effect, you could pass in a type of decay to the animate function.

import { animate } from "popmotion";

animate({
  from: 0,
  to: 100,
  type: "decay",
  onUpdate: (latest) => {},
});

Spring Animation

Tying this together with the previous section, to create a spring animation for your element, you would pass in a from and a to value to the animate function and update the element's properties in the onUpdate callback. This is how you would add a scale stretch animation like the demo above 👇

import { ElementRef, ViewChild } from '@angular/core';
import { animate } from 'popmotion';

...
export class YourComponent {

    @ViewChild('#element') element: ElementRef;

    startAnimation(): void {
        animate({
            from: '1,1',
            to: '1.2,0.8',
            duration: 200,
            onUpdate: (latest) => {
                const [scaleX, scaleY] = latest.split(',').map((val) => +val);
                this.element.nativeElement.scaleX = scaleX;
                this.element.nativeElement.scaleY = scaleY;
            },
            onComplete: () => {
                animate({
                    from: '1.2,0.8',
                    to: '1,1',
                    duration: 1000,
                    type: 'spring' // <- Note that this one has type spring to get the bouncy effect
                    onUpdate: (latest) => {
                        const [scaleX, scaleY] = latest.split(',').map((val) => +val);
                        this.element.nativeElement.scaleX = scaleX;
                        this.element.nativeElement.scaleY = scaleY;
                    }
                })
            }
        })
    }
}

Angular Tip

The onUpdate callback fires frequently throughout the animation. This triggers Angular's change detection multiple times - which is to be expected. By default, if the code runs inside NgZone, change detection is triggered every time the last function in the call stack is executed.

You can verify this by adding the following code in your component and checking the number of times the message gets logged in your console.

ngDoCheck(): void {
    console.log('Change detection triggered!');
}

To fix this and optimize our animation, we can reduce the number of times change detection gets triggered. We can do this by wrapping the animation code with a NgZone.runOutsideAngular like this 👇

import { NgZone } from '@angular/core';
import { animate } from "popmotion";

export class YourComponent {

    constructor(private ngZone: NgZone) {}

    startAnimation(): void {
        this.ngZone.runOutsideAngularZone(() => {
            animate({
                ...
            })
        });
    }
}

Running the animation code outside of NgZone will prevent change detection from getting triggered due to the onUpdate callback. Angular isn't aware that these callbacks are getting called and as a result doesn't trigger change detection.

That brings us to the end of the tutorial. If you have any questions or comments about the post, leave them in the comments below, or get in touch with me on Twitter @williamjuan27.


William is a Nativescript Ambassador based in Chicago, IL. He is a front end enthusiast with a love for all things web.

Did you enjoy this? Share it!

Take control of your career. Build JavaScript mobile apps.