Hi, I'm Emily.

eehayman

#smashingplug

performant web animations

PERFORMANT PAGE LOAD IS JUST THE PROLOGUE.

 

ANIMATIONS

MAKE

INTERACTIONS

IMPACTFUL

it's all about the user

aim for 60 frames per second

Frame rate measures responsiveness

LET'S TAKE A STEP BACK.

HOW IS THE BROWSER GENERATING PIXELS FROM YOUR CODE?

  • Download and parse HTML, CSS, JS
  • Calculate element styles

layout

paint

COMPOSITE

layout

GEOMETRY OF THE PAGE

  • Generate layout of the page from the render tree
  • Highly interdependent

paint

filling in the pixels

  • Calculation of visual styles
  • Re-paints only occur one per frame

composite

generating the layers

  • Separation of elements onto compositor layers
  • Allows for non-destructive changes

how can i use this?

ONLY CHANGE PROPERTIES THAT TRIGGER COMPOSITING.  

transform

opacity

you'll have to get creative.

transform

  • Transform an element
  • Position (transform: translateX(5px))
  • Scale (transform: scale(2))
  • Rotation (transform: rotate(90deg))
  • Skew (transform: skewX(50deg))
  • Matrix (transform: matrix3d(...))
.box {
    left: 10px;
    position: absolute;
    transition: .2s;
    &.active {
        left: 20px;
    }
}
.box {
    transform: translateX(10px);
    position: absolute;
    transition: .2s;
    &.active {
        transform: translateX(20px);
    }
}

opacity

hide & show elements

.menu {
  opacity: 1;
  transition: .2s;
}

.menu.closed {
  opacity: 0;
  pointer-events: none;
}

simplify mobile menus

toggle visibility level

.box {
    box-shadow: 1px 1px 1px rgba(0,0,0,.5);
    transition: .2s;
}
.active {
   box-shadow: 1px 1px 1px rgba(0,0,0,1);
}
.box {
   position: relative;
}
.box:before {
   content: “”;
   box-shadow: 1px 1px 1px rgb(0,0,0);
   opacity: .5;
   transition: .2s;
   position: absolute;
   width: 100%;
   height: 100%;
}
.box.active:before {
   opacity: 1;
}

what can i control?

MANUALLY PROMOTE ELEMENTS TO THEIR OWN COMPOSITOR LAYER

  • Forcing promotion ensures layer is painted and ready
  • Basically, tell the browser what needs help

what needs help?

anything that is paint expensive

  • Elements that will be changed
  • And more! (fixed: position; overflow: scroll)

ok, but how??

The old method

"Trick" the browser to promote the element

.box {
    backface-visiblity: hidden;
}

.box {
    transform: translate3d(0,0,0);
}

the shiny, new method

will-change

Tell the browser what's needed ahead of time

.box {
    will-change: auto; //default, standard optimizations
    will-change: scroll-position; 
    will-change: contents; 
    will-change: transform, opacity; //define property(s)
}

87.1% support!

moderation.

THERE CAN BE TOO MANY COMPOSITED LAYERS

  • will-change optimizations are resource heavy
  • Usage implies an element is always moments away from changing

prove it.

They say a picture's worth 1000 words...

@keyframes move {
  0%   { left: 0; }
  50% { left: 300px; font-size: 200px }
  100% {left: 0; }
}
.wrapper {
   animation: move 2s infinite linear;
   display: inline-flex;
   position: absolute;
   font-size: 100px;
}
@keyframes move {
  0%   { transform: translateX(0); }
  50% { transform: translateX(300px) scale(2);}
  100% {transform: translateX(0); }
}
.wrapper {
   animation: move 2s infinite linear;
   display: inline-flex;
   position: absolute;
   font-size: 100px;
}

Text

IMPERATIVE VS. DECLARATIVE

CSS (Declarative)

Javascript (Imperative)

choose the best tool for the job.

DECLARATIVE ANIMATIONS

utilize css

  • Browser knows the end point of the operation
  • Runs off the main thread
  • More performant

keyframes ftw

@keyframes spin {
    from {transform: rotate(0deg);}
    to {transform: rotate(360deg);}
}
.box {
   animation-name: spin;
   animation-duration: 3ms;
   animation-iteration-count: infinite;
   animation-timing-function: linear;
}

IMPERATIVE ANIMATIONS

utilize javascript

  • More responsive to user input
  • Runs on the main thread
  • Less performant

spin... on click!

var box = document.getElementById("box")
box.addEventListener("click", function(){
    box.classList.toggle("spin");
});

what if i need js?

REQUESTANIMATIONFRAME

  • Native API for running an animation
  • Browser optimizes for performance
function doSomething() {
    requestAnimationFrame(doSomething);
    // Do stuff
}
doSomething();

window.addEventListener("scroll", function () {
    window.requestAnimationFrame(toggleCollapsedClass);
});

Speaking of scroll performance...

Passive event listeners

element.addEventListener('touchmove', doSomething(), { passive: true });

Intersection Observer API

avoid layout thrashing

boxes.forEach(box => {
    box.style.transform = “translateY(“ + wrapper.getBoundingClientRect().height + “px)”;
})
var wrapperHeight = wrapper.getBoundingClientRect().height + 'px';
boxes.forEach(box => {
    box.style.transform = “translateY(“ + wrapperHeight + “px)”;
})

above and beyond!

css containment

  • Indicate an element's subtree is independent from the rest of the page
  • Tells the browser it's safe to optimize an element
.box {
    contain: strict; //all rules
    contain: content; //all rules but size
    contain: size; //element can be sized w/ no need to check children
    contain: layout; //internal layout is 'contained' from external
    contain: style; //limits scope of styles to element & children
    contain: paint; //indicates children visibility is limited
}

you'll have to get hacky

testing animations

chrome developer tools

timeline

CPU Throttling

Test

&

Iterate

... your users will thank you!

thanks!

eehayman

Performant Web Animations

By ehayman

Performant Web Animations

  • 2,346