60fps
● Majority of device refresh screen 60 times per second
○ 1 frame = 16.6ms(1000ms ÷ 60)
● If frame is not created under 16ms, framerate drops
⇒ Jank happens
○ too much of it and you die
Slide 5
Slide 5 text
What is a Layer?
Slide 6
Slide 6 text
● JavaScript: work that will result in visual changes (not JavaScript only)
● Style: calculate the final styles for each element by figuring out which
CSS rules apply to which elements.
● Layout (reflow): calculate dimensions and position for every element
based on style
● Paint: filling in pixels for every layers
● Composite: draw all layers in the right order to the screen
Pixel Pipeline
Slide 7
Slide 7 text
JS > Style > Layout > Paint > Composite
● What about performance?
○ Less work ⇒ Better performance
● Do we need to pass through all these jobs?
● JS / Style / Composite:Required
⇒ What about Layout and Paint?
○ Depends on the style
Slide 8
Slide 8 text
JS > Style > Layout > Paint > Composite
Layout property ⇒
● Width, height, position change can affect any element, the
browser needs to re-Layout everything
● The affected area needs to be re-Paint
Slide 9
Slide 9 text
JS > Style > Paint > Composite
Paint property ⇒
● Color, shadow, background-image kind of change
● Nothing to do with the size nor dimension, Layout is skipped
● Still need to run Paint
Slide 10
Slide 10 text
JS > Style > Composite
Composite property ⇒
● Change at the layer level, nothing inside is changed
A reference for the render impact of mutating CSS properties:
https://csstriggers.com/
Slide 11
Slide 11 text
Use of the Pixel Pipeline at Our Advantage
What can we do to improve performance?
Each step of the pipeline in detail
Slide 12
Slide 12 text
JavaScript
Slide 13
Slide 13 text
Animation with JavaScript
● Usage of setTimeout or setInterval for animation is bad
○ We cannot predict their execution
Slide 14
Slide 14 text
Animation with JavaScript
● If setInterval > 60fps, overwork
● If setInterval < 60fps, jank
● If setInterval = 60fps, needs to be synced with the screen
refresh timing
● There is still many 50Hz devices in Europe
⇒ requestAnimationFrame
Slide 15
Slide 15 text
Window.requestAnimationFrame()
function step() {
// Animation step, e.g. translateX += n;
// Register a step for the next frame
requestAnimationFrame(step);
}
// Register a step for the next frame
requestAnimationFrame(step);
Slide 16
Slide 16 text
requestAnimationFrame() Support
● IE10 ≧ OK
● Others are fine
● Polyfill exists
⇒ No excuse not to use requestAnimationFrame
Slide 17
Slide 17 text
Main Thread (UI Thread)
● Job on the Main Thread: Style, Layout, Paint and JavaScript
● If all jobs combined take more than 16ms ⇒ Jank
● During JavaScript execution, all other jobs are blocked
Slide 18
Slide 18 text
Rendering VS JavaScript
http://oldergod.github.io/ui-rendering-opt/demos/main-thread/index.html
Slide 19
Slide 19 text
Main Thread (UI Thread)
⇒ Enter Web Workers
Slide 20
Slide 20 text
Web Worker
➕ Runs on a different thread
➕ Does not affect rendering
➕ XMLHttpRequest, IndexedDB... OK
➖ No DOM access
Slide 21
Slide 21 text
Web Worker Demo
http://oldergod.github.io/ui-rendering-opt/demos/web-workers/index.html
Slide 22
Slide 22 text
// worker registration
const worker = new Worker('scripts/worker.js');
// Send message to the worker inside manipulateImage
worker.postMessage({ type, imageData });
// Received a message from the worker
worker.onmessage = function(message) {
const imageData = message.data;
toggleButtonsAbledness();
ctx.putImageData(imageData, 0, 0);
};
// worker termination
worker.terminate();
Web Worker Demo
function onmessage(data);
function postMessage(data);
Web Worker
Thread
Slide 23
Slide 23 text
// worker registration
const worker = new Worker('scripts/worker.js');
// Send message to the worker inside manipulateImage
worker.postMessage({ type, imageData });
// Received a message from the worker
worker.onmessage = function(message) {
const imageData = message.data;
toggleButtonsAbledness();
ctx.putImageData(imageData, 0, 0);
};
// worker termination
worker.terminate();
Web Worker Demo
function onmessage(data);
function postMessage(data);
Web Worker
Thread
Slide 24
Slide 24 text
// worker registration
const worker = new Worker('scripts/worker.js');
// Send message to the worker inside manipulateImage
worker.postMessage({ type, imageData });
// Received a message from the worker
worker.onmessage = function(message) {
const imageData = message.data;
toggleButtonsAbledness();
ctx.putImageData(imageData, 0, 0);
};
// worker termination
worker.terminate();
Web Worker Demo
function onmessage(data);
function postMessage(data);
Web Worker
Thread
Slide 25
Slide 25 text
// worker registration
const worker = new Worker('scripts/worker.js');
// Send message to the worker inside manipulateImage
worker.postMessage({ type, imageData });
// Received a message from the worker
worker.onmessage = function(message) {
const imageData = message.data;
toggleButtonsAbledness();
ctx.putImageData(imageData, 0, 0);
};
// worker termination
worker.terminate();
Web Worker Demo
function onmessage(data);
function postMessage(data);
Slide 26
Slide 26 text
// worker registration
const worker = new Worker('scripts/worker.js');
// Send message to the worker inside manipulateImage
worker.postMessage({ type, imageData });
// Received a message from the worker
worker.onmessage = function(message) {
const imageData = message.data;
toggleButtonsAbledness();
ctx.putImageData(imageData, 0, 0);
};
// worker termination
worker.terminate();
Web Worker Demo
function onmessage(data);
function postMessage(data);
Web Worker
Thread
Slide 27
Slide 27 text
Web Worker Support
● IE10 ≧ OK
● Others are fine
● Polyfill exists
⇒ No excuse not to use Web Workers
Slide 28
Slide 28 text
Layout
Slide 29
Slide 29 text
Complex Layout & Layout Thrashing
● Layout’s scope is usually the whole document
○ If only one element changes, all have to be re-Layout
○ The more elements, the higher cost
⇒ Then what?
○ Skip Layout by using Paint or Composite properties only
○ Audit with the DevTools to find bottlenecks
Layout Thrashing Audit
// Usage of last calculated Layout data
offsetWidth = greenBar.offsetWidth + 'px';
paragraphs[i + 1].style.width = offsetWidth;
offsetWidth = greenBar.offsetWidth + 'px';
paragraphs[i].style.width = offsetWidth;
Slide 36
Slide 36 text
Layout Thrashing Audit
// Usage of last calculated Layout data
offsetWidth = greenBar.offsetWidth + 'px';
paragraphs[i + 1].style.width = offsetWidth;
offsetWidth = greenBar.offsetWidth + 'px';
paragraphs[i].style.width = offsetWidth;
Slide 37
Slide 37 text
Layout Thrashing Audit
// Usage of last calculated Layout data
offsetWidth = greenBar.offsetWidth + 'px';
// Change in style ⇒ Layout data are invalidated
paragraphs[i + 1].style.width = offsetWidth;
offsetWidth = greenBar.offsetWidth + 'px';
paragraphs[i].style.width = offsetWidth;
Slide 38
Slide 38 text
Layout Thrashing Audit
// Usage of last calculated Layout data
offsetWidth = greenBar.offsetWidth + 'px';
// Change in style ⇒ Layout data are invalidated
paragraphs[i + 1].style.width = offsetWidth;
offsetWidth = greenBar.offsetWidth + 'px';
paragraphs[i].style.width = offsetWidth;
Slide 39
Slide 39 text
Layout Thrashing Audit
// Usage of last calculated Layout data
offsetWidth = greenBar.offsetWidth + 'px';
// Change in style ⇒ Layout data are invalidated
paragraphs[i + 1].style.width = offsetWidth;
// Layout data had been invalidated so need to re-Layout
offsetWidth = greenBar.offsetWidth + 'px';
paragraphs[i].style.width = offsetWidth;
Slide 40
Slide 40 text
Layout Thrashing Audit
// Usage of last calculated Layout data
offsetWidth = greenBar.offsetWidth + 'px';
// Change in style ⇒ Layout data are invalidated
paragraphs[i + 1].style.width = offsetWidth;
// Layout data had been invalidated so need to re-Layout
offsetWidth = greenBar.offsetWidth + 'px';
paragraphs[i].style.width = offsetWidth;
Slide 41
Slide 41 text
Layout Thrashing Audit
// Usage of last calculated Layout data
offsetWidth = greenBar.offsetWidth + 'px';
// Change in style ⇒ Layout data are invalidated
paragraphs[i + 1].style.width = offsetWidth;
// Layout data had been invalidated so need to re-Layout
offsetWidth = greenBar.offsetWidth + 'px';
// Change in style ⇒ Layout data are invalidated, etc
paragraphs[i].style.width = offsetWidth;
Slide 42
Slide 42 text
Layout Thrashing Audit
// Usage of last calculated Layout data
offsetWidth = greenBar.offsetWidth + 'px';
// Change in style ⇒ Layout data are invalidated
paragraphs[i + 1].style.width = offsetWidth;
// Layout data had been invalidated so need to re-Layout
offsetWidth = greenBar.offsetWidth + 'px';
// Change in style ⇒ Layout data are invalidated, etc
paragraphs[i].style.width = offsetWidth;
Slide 43
Slide 43 text
Layout Thrashing Fix
while (i--) {
paragraphs[i].style.width = greenBar.offsetWidth + 'px';
}
let offsetWidth = greenBar.offsetWidth + 'px';
while (i--) {
paragraphs[i].style.width = offsetWidth;
}
Slide 44
Slide 44 text
Layout Thrashing
● Layout data are from last created frame
● If Style changes, Layout data are invalidated
⇒ Get Style & Layout only once, before changing anything
⇒ Let’s keep the Pixel Pipeline order in mind
Slide 45
Slide 45 text
Paint
Slide 46
Slide 46 text
Let’s keep Paint simple
● Paint is usually the most costly job in the pipeline
Slide 47
Slide 47 text
● By using multiple layers, we can tell the browser to only Paint
the affected layer
⇒ What about Composite properties?
Minimize Paint’s scope
How do you create a Layer?
Composite only property
⚠ Condition:The affected element needs to be in its own layer to
be able to skip Layout/Paint
⇒ Let’s promote this element to create its own Layer
Slide 51
Slide 51 text
How to make a Layer
1. Official: will-change: transform;
2. Hacky: transform: translateZ(0);
○ For when will-change is not supported
Use will-change: transform; or transform: translateZ(0);
* {
will-change: transform;
transform: translateZ(0);
}
Element promotion
// NG because of Management
// and Memory cost
Slide 54
Slide 54 text
will-change Support
● IE、Edge (14): no-go
● Recent builds of other browsers: OK
Slide 55
Slide 55 text
Rendering Performance
Wrapping up
● Always audit before optimize
● No need to fix something that is not broken
⇒ Always Audit First
Slide 56
Slide 56 text
Rendering Performance
Wrapping down
● Less work
● Scheduled work
● Isolated work
● Smooze work
⇒ Happy User
Slide 57
Slide 57 text
Fin
#perfmatters
格好良いエンジニア募集中!
http://goo.gl/9GK409
Slide 58
Slide 58 text
Reference
● Google Web Fundamentals: Rendering Performance
○ https://developers.google.com/web/fundamentals/performance/rendering/
● Udacity free course on Rendering Performance
○ https://www.udacity.com/course/browser-rendering-optimization--ud860
● Repository of the demos
○ https://github.com/oldergod/ui-rendering-opt
● Render impact of mutating CSS properties reference
○ https://csstriggers.com/
● Writing Efficient CSS
○ https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Writing_efficient_CSS
● BEM-like CSS
○ https://en.bem.info
● Cheap animation with FLIP
○ https://github.com/googlechrome/flipjs
● The RAIL Performance Model
○ https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/rail