Time to FLIP - High Performance Animations

When you are animating something on a website, you obviously want it to run smooth on all your devices. After all, the use of animations just makes a site feel so much more fun to visit. When these animations don’t go smooth like they’re supposed to, your website will feel way less professional. In this blogpost we’ll show you how to make animations perform better using the four steps of FLIP animations.

The way of the FLIP

The hard part of animating a website is performance, specifically on mobile devices. The browser uses your device's CPU to render an animation, and a smooth animation needs to render at 60 FPS (Frames Per Second). So the better the CPU, the smoother your animation will be.

CPUs on mobile devices are weaker and find it therefore more difficult to render animations at a steady rate of 60 FPS. The result? An animation that looks fabulous on a desktop, but crappy on a mobile device.

To make animations run as smooth as possible, we need to use FLIP. You could say that FLIP is like a principle or a new way of thinking about animations. Every letter in FLIP stands for four steps that you need to take in animating an element:

  • First: In this step we need to store the first state of the element (start of the animation);
  • Last: Here we need to store the final state of the element (end state of the animation);
  • Invert: In this step, we calculate the element differences of the FIRST and the LAST state (this is where the magic happens);
  • Play: Finally, we apply the inverted state and release when an interaction is made.

Before we show you how the four steps of the FLIP principle works, we did some simulations to show the difference between the JavaScript way, the CSS way and the FLIP way. To do these simulations, we used Chrome DevTools.

In the performance tab we adjusted the CPU throttling to 20x slower than usual, which will simulate a very slow CPU (imagine the worst mobile device ever). In our simulations we used a cube. We made this cube move from left to right and, while moving, grow in size.

The JavaScript way

Slowmo JavaScript animation

Video JavaScript animation

In the video it looks like the cube is just switching places, instead of smoothly going to his new position and size. The image explains why: The bar chart represents the animation of 200ms; you see a red bar and a green bar. The red bar is where the browser is re-calculating the entire screen while the animation is running in the first 150ms. The green bar stands for the FPS of the animation. Here, the animation has almost 0 FPS at the beginning, but towards the end, at about 150/160ms, it has like 10 FPS. That’s why, in the video, it is like the cube is just switching places.

The CSS way

Slowmo CSS animation

Video CSS animation

In this video it looks like the animation is playing smoothly but, as you can see in the bar graph, it actually isn’t. Much as in the JavaScript animation, the red bar means it is doing some calculations in the first 40ms, however, the animation doesn’t even play in the first milliseconds. After the calculations have been done, you see the green bar. This means the CSS animation is playing at about 30 FPS.

The FLIP way

Slowmo FLIP animation

Video FLIP animation

In this third video you see a fluent animation. In the bar chart you see that there is no red bar, since the calculations are already made. The only thing the browser has to do is play the animation, and it runs at an average of 60 FPS in the full 200ms. The lowest FPS here is 30, which is still good considering the fact that, in our simulations, we slow down the CPU by a factor of 20.

FLIP or First, Last, Invert, Play

Below we will break down every step of the FLIP with some example code to explain how it works. When implementing FLIP, these steps are necessary to take. If you’re curious about the result of these steps, see this example of a FLIP animation on CodePen.

FIRST

FIRST, we need to get the initial state of the element and put it in a variable. We need to do this because we want to remember both the start and the end position of the elements to animate. Here we will store the start position. In this example the cube moves from left to right and it also grows in size, so we will need left, width and height.

This is where Javascript method getBoundingClientRect(); comes in handy. We want to animate the size and the position of the element. getBoundingClientRect(); will return “positioning” and “size”, which is exactly what you need from the element.

let small = this.getBoundingClientRect()

LAST

LAST, we will need to store the end state of the animation. We do this by adding the class “big” that will move and expand the object first. After the class is added, we also need to use getBoundingClientRect(); to know the element's “size” and “position”. Then we store the properties in a variable to use in the third step.

this.classList.add('growing');
this.classList.remove('small');
this.classList.add('big');
let big = this.getBoundingClientRect();

INVERT

After that, you need to INVERT these states. This is where the FLIP magic actually happens. The first position becomes last and the last position becomes first. We store these in variables to use them in the final step.

let invertedLeft = big.left - small.left;
let invertedWidth = big.width / small.width;
let invertedHeight = big.height / small.height;

PLAY

Finally, you need to PLAY the animation by first adding the “transform” properties with the calculations to the element and then removing the “transform” property. Now CSS will take over with its “transition” property transition: transform 200ms ease;. This will slowly reduce the value “transform” with the given duration of 200ms. We use “transform” because it works on the composite layer (see below).

this.style.transform = 'translateX(' + invertedLeft + 'px) translateZ(0) scale(' + invertedWidth + ', ' + invertedHeight + ')';

this
.addEventListener('transitionend', function handler() {
    this.classList.remove('transition');
    this.style.transform = '';
    this.classList.remove('growing');
    this.classList.remove('small');
    this.classList.add('big');
    this.removeEventListener('transitionend', handler);
});

Resulting code

let elem = document.querySelector('.js-flip');  // to get the element

  elem.addEventListener('click', function(){

    if (this.classList.contains('small')) {

      /*  FIRST: Store the initial size and position  */
      let small = this.getBoundingClientRect();

      /*  LAST: Store the size and position like it would be when the animation is complete  */
      this.classList.add('growing');
      this.classList.remove('small');
      this.classList.add('big');
      let big = this.getBoundingClientRect();
      this.classList.remove('big');
      this.classList.add('small');
      this.classList.add('transition');

      /*  INVERT: Calculate differences and store them in variables  */
      let invertedLeft = big.left - small.left;
      let invertedWidth = big.width / small.width;
      let invertedHeight = big.height / small.height;


      /*  PLAY: Add transform and release the animation  */
      this.style.transform = 'translateX(' + invertedLeft + 'px) translateZ(0) scale(' + invertedWidth + ', ' + invertedHeight + ')';

      this.addEventListener('transitionend', function handler() {
        this.classList.remove('transition');
        this.style.transform = '';
        this.classList.remove('growing');
        this.classList.remove('small');
        this.classList.add('big');
        this.removeEventListener('transitionend', handler);
      });

    }

  });

 

The code above uses the following CSS (SCSS) set-up:

.m-flip__box {
  background-color: lightblue;
  position: absolute;

  &.small {
    width: 50px;
    height: 50px;
  }

  &.big {
    height: 200px;
    width: 200px;
    left: 300px;
  }
}  

.growing {
  transform-origin: bottom left;
  &.transition {
    transition: transform 250ms ease;
  }
}

You can find an example of the result on CodePen.

Things to keep in mind

Remember: To animate with FLIP, you will still need CSS and JavaScript. As usual, you need to check if the browsers that your project has to work on are supporting the properties used in your animations. In our example, there is support for all modern browsers and even IE9.

Also, keep in mind that there are properties that trigger the layout, paint and composite layers in the browser like top, left, width, height and many more. To make animation run smooth, we need to work only on the composite layer of the browser. In the example of this blogpost we have only focused on “transform”; the reason for this is that it only works on the composite layer. If you want to know more about this, see Smooth as Butter: Achieving 60 FPS Animations with CSS3. For other properties that render on the composite layer, check CSS Triggers.

Conclusion

So now you know the four steps of the FLIP Principle: First, Last, Invert and Play. Basically, FLIP does some pre-calculations and makes it so that the browser doesn’t need to work that hard to make an animation run smooth. The hard part is not to set up the code, but to think the FLIP way when animating an element. Hopefully now you will have no trouble implementing FLIP in your projects.

Note that, apart from the example above, you can apply FLIP on many animations. Take a look at these FLIP Demos. In the “Docs” section, you can find another library that will make it even easier for you to build animations using FLIP.

Tags: JavaScript FLIP CSS

June 22, 2017 by Marijn Rutten