Like clockwork, or animate at 60 FPS on CSS 3

Original author: Jose Rosario
  • Transfer
  • Tutorial

Images and text belong to their authors.


Animating elements in mobile apps is easy. Correct animation can also be simple ... if you follow the tips in this article.


Today, whoever does not use CSS 3 animation in their projects, nevertheless, not only everything, but few can do it right. Even the so-called "best practices" are described, but people continue to do things their own way. Most likely because they simply do not understand why everything is arranged this way, and not otherwise.



The range of mobile devices only does what is expanding, so if you do not optimize your code with this in mind, this approach will sooner or later make itself felt in the form of application brakes that your users will encounter.


Remember: in spite of the fact that there are several flagship devices in the world constantly pushing progress forward, people use their favorite antiques, which they are reluctant to part with.


Using CSS 3 correctly will fix some of the problems, so we want to help you understand some things.


Taming time


What does the browser do in the process of rendering and managing all of these elements on the page? The answer is this simple timeline called CRP (Critical Rendering Path).


image

To achieve smooth animations, we need to focus on changing the properties that affect the Composite step .


Styles


image

The browser starts calculating styles to apply them to the elements.


Frame


image

At this stage, the browser generates and determines the position of the elements on the page. It is at this point that the browser sets the page attributes, such as width, height, indentation, and others.


Drawing


image

The browser forms the elements as separate layers, applying properties such as box-shadow , border-radius , color , background-color and so on.


Overall picture


image

It is at this stage that your magic will be required, since right now the browser draws all the formed layers on the screen. Modern browsers perfectly animate four types of properties, operating with transformation and translucency.


  1. Position. transform: translateX ( n ) translateY ( n ) translateZ ( n );
  2. Scaling. transform: scale ( n );
  3. Turn. transform: rotate ( n deg);
  4. Translucency. opacity: n ;

How to reach 60 FPS


Let's start with HTML and create a simple structure for the application menu inside the container .layout.



image

Wrong Way


.app-menu {
  left: -300px;
  transition: left 300ms linear;
}
.app-menu-open .app-menu {
  left: 0px;
  transition: left 300ms linear;
}

Noticed what properties we are changing? Avoid using transformations with left / top / right / bottom properties . They do not allow you to create smooth animation, because they force the browser to reassemble the layers each time, and this will affect all children.


The result is something like this:


image

Animation is slow. We checked the DevTools timeline to see what really happens, and here is the result:


image

The picture clearly shows the instability of FPS and, as a result, poor performance.


Using Transforms


app-menu {
  -webkit-transform: translateX(-100%);
  transform: translateX(-100%);
  transition: transform 300ms linear;
}
.app-menu-open .app-menu {
  -webkit-transform: none;
  transform: none;
  transition: transform 300ms linear;
}

Unlike the above properties, transformations are applied to already drawn blocks, that is, at the Composite stage . In the example above, we kind of tell the browser that before the start of the animation, all layers will already be drawn and ready for manipulation.


image

The timeline shows that the FPS has become more even, so the animation will look somewhat smoother.


image

GPU animation


Everything could be even better. For this we will use a graphics accelerator.


.app-menu {
  -webkit-transform: translateX(-100%);
          transform: translateX(-100%);
  transition: transform 300ms linear;
  will-change: transform;
}

While translateZ () or translate3d () is still required by some browsers, like some hack, the future will-change property . It tells the browser to move the elements to a separate layer so that it then does not check the entire wireframe for assembly or rendering.


image

See how smooth the animation has become? Timeline confirms this:


image

FPS has become even more stable, but still there is one slow piece of animation in the beginning. Based on the menu structure, JS usually writes something like this:


function toggleClassMenu() {
  var layout = document.querySelector(".layout");
  if(!layout.classList.contains("app-menu-open")) {
    layout.classList.add("app-menu-open");
  } else {
    layout.classList.remove("app-menu-open");
  }
}
var menu = document.querySelector(".menu-icon");
menu.addEventListener("click", toggleClassMenu, false);

The problem is that adding a class to the container .layout, we force the browser to recount the styles again, and this affects the speed of layout and rendering.


Like clockwork


But what if the menu were located beyond scope? Having done this, we would use only the element that really needs to be animated, that is, our menu. For clarity, the HTML structure:



Now we can control the status of the menu in a slightly different way. We will control the animation through a class that is deleted after the animation ends using the transitionend event .


function toggleClassMenu() {
  myMenu.classList.add("menu--animatable");
  myMenu.classList.add("menu--visible");
}
function onTransitionEnd() {
  myMenu.classList.remove("menu--animatable");
}
var myMenu = document.querySelector(".menu"),
    menu = document.querySelector(".menu-icon");
myMenu.addEventListener("transitionend", onTransitionEnd, false);
menu.addEventListener("click", toggleClassMenu, false);

Well, now all together. Your attention is a complete example of CSS 3, where everything is in place:


.menu {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  pointer-events: none;
  z-index: 150;
}
.menu—visible {
  pointer-events: auto;
}
.app-menu {
  background-color: #fff;
  color: #fff;
  position: relative;
  max-width: 400px;
  width: 90%;
  height: 100%;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
  -webkit-transform: translateX(-103%);
  transform: translateX(-103%);
  display: flex;
  flex-direction: column;
  will-change: transform;
  z-index: 160;
  pointer-events: auto;
}
.menu—-visible.app-menu {
  -webkit-transform: none;
  transform: none;
}
.menu-—animatable.app-menu {
  transition: all 130ms ease-in;
}
.menu--visible.menu—-animatable.app-menu {
  transition: all 330ms ease-out;
}
.menu:after {
  content: ‘’;
  display: block;
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,0.4);
  opacity: 0;
  will-change: opacity;
  pointer-events: none;
  transition: opacity 0.3s cubic-bezier(0,0,0.3,1);
}
.menu.menu--visible:after{
  opacity: 1;
  pointer-events: auto;
}

image

And what does the timeline show?


image

Something like this.


Also popular now: