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,458