PERFORMANT web ANIMATIONS
Achieving 60FPS
Emily Hayman
- Awkward
- Unnatural
- Janky
- Slow
WHY?
prevent poor User experience
"Silky smooth" feels fast
- Frame rate measures a site's responsiveness
- 60fps is the target for a natural feel
- =16.7ms to achieve all necessary updates
- Caveat: More frames, more processing - might result in dropped frames
- Better to deliver lower frame rates more consistently (30fps)
What?
AIM FOR 60 FRAMES PER SECOND
On initial page load:
- Download and parse HTML, CSS, JS
- Evaluate JS
- Calculate element styles
Let's take a step back.
How is the browser generating pixels from your code?
- Generate layout of the page from the render tree
- Where elements are placed is correlated to other elements
- Forced synchronous layouts (layout thrashing) caused by multiple read / writes
- Re-layouts are expensive
Layout
geometry of the page
- Calculation of visual styles
- Re-paint will only occur once per frame - will only re-draw "dirty" elements
- Browser will attempt to consolidate re-paint regions in a single "dirty" rectangle - may need to promote layers to prevent side effects
- Re-paints are expensive
PAINT
FIlling in the pixels
- Default is to paint elements on a single memory layer
- Separating elements onto compositor layers allows non-destructive changes
- Only necessary work is to calculate the new position for each layer
- CPU draws the layers; GPU composites the layers
- GPU is very efficient at basic drawing operations
- Changes on GPU-composited layers are the least expensive
Composite
generating the layers
- Transform
- Opacity
- Seriously, that's it.
How do i take advantage of this?
only change properties that trigger compositing
Transform allows:
- Position (transform: translateX(5px))
- Scale (transform: scale(2))
- Rotation (transform: rotate(90deg))
- Skew (transform: skewX(50deg))
- Matrix (transform: matrix3d(...))
Get Creative
transform
Instead of:
Get Creative
transform
.box {
left: 10px;
position: absolute;
transition: .2s left;
&.active {
left: 20px;
}
}
Try this:
.box {
transform: translateX(10px);
transition: .2s transform;
&.active {
transform: translateX(20px);
}
}
Instead of:
Get Creative
Opacity
.box {
background-color: rgba(0,0,0,.9);
transition: .2s background-color;
&.active {
background-color: rgba(0,0,0,1);
}
}
Try this:
.box {
opacity: .9;
background-color: #000;
transition: .2s opacity;
&.active {
opacity: 1;
}
}
- GPU handles opacity changes by painting the element texture with a lower alpha value
- Must be on its own layer
Get Creative
Opacity
- Forcing promotion ensures layer is painted and ready
- Consider promoting expensive paint elements (position: fixed; overflow: scroll;)
- Will fix "flickering" or "shimmy"
What else can i do?
manually promote elements to their own compositor layer
-
backface-visibility: hidden;
- transform: translate3d(0,0,0);
-
"Tricks" the browser into promoting the element
the old way
"tricking" the browser
- Provides method for informing the browser what types of optimizations will be needed ahead of time
- Current support: Chrome + Firefox
The shiny, new method
will-change
- will-change: auto - default; standard optimizations
- will-change: scroll-position - indicates upcoming animation of an element's scroll position
- will-change: contents - indicates content is expected to change
- will-change: <property> - define property (for example, "transform")
The shiny, new method
will-change
- The browser already does its best to optimize - stronger will-change optimizations are resource heavy
- If animating non-GPU properties, browser forced to re-paint on the CPU and then upload to the GPU - can potentially be even more costly
- Using will-change implies element is always moments away from changing
as in all things, moderation
there can be too many composited layers
quick example
what not to do
quick example
what to do
imperative vs. declarative
how do i best accomplish my goal?
Two methods of creating web animations:
- CSS (Declarative)
- Javascript (Imperative)
declarative animations
utilize css
- Browser can make optimizations - it knows the end point of the operation
- Can run operations off the main thread
- Missing the expressive power of JS animations; may grow overly complex
- Recommend toggling a class using JS for basic user input actions
- Generally, the more performant option
Imperative animations
utilize Javascript
- Runs on the main thread - more likely to result in dropped frames
- More control - responsive to user input; start/ stop easily
- Generally, the less performant option
What if i need to use JS?
requestanimationframe
- The setTimeout of the future - native API for running an animation
- Typically called at 60fps; requests animation drawing at the next available opportunity - not a set interval
- Browser optimizes performance and groups into a single repaint (saves CPU cycles)
- IE10+ support
requestanimationframe
function doSomething() {
requestAnimationFrame(doSomething);
// Do stuff
}
doSomething();
What if i need to use JS?
avoid layout thrashing
- Read, then write
- Back and forth read / writing will cause reflows
- Browser tracks "dirty" elements and queues up changes until necessary; reading certain properties forces premature re-calculations
for(i = 0; i < el.length; i++){
var width = element.offsetWidth * 2;
el[i].style.width = width + 'px';
}
var width = element.offsetWidth * 2;
for(i = 0; i < el.length; i++){
el[i].style.width = width + 'px';
}
Bad
Good
upcoming optimizations
css containment
- "contain" property: indicate an element's subtree is independent from the rest of the page
- Lets the browser know it's safe to optimize an element
- Can indicate "layout", "style", or "paint" - or "strict" for all
- Ensure DOM updates in the subtree do not trigger reflows on parent document
testing animations
chrome developer tools
Rendering Tools:
- Enable paint flashing
- Show layer borders
- Show FPS meter
Timeline
Questions?
Performant Web Animations
By ehayman
Performant Web Animations
- 11,130