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