Browser Rendering Optimization
Building 60 FPS Web Apps
Oleksii Kachura
Things to talk about
- Critical Rendering Path
- App Lifecycles
- Tools
- Optimizations
- JavaScript
- Styles
- Layout
- Painting and Compositing
Why?
- We want our apps to be smooth.
- Users want our apps to be smooth.
Critical Rendering Path
From raw bytes to pixels on your screen (in 1s)
CRP
CRP
HTTP/2
Example
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance </span>students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
/* style.css */
body { font-size: 16px; }
p { font-weight: bold; }
span { color: red; }
p span { display: none; }
img { float: right; }
HTML
Conversion
Tokenizing
Lexing
DOM construction
CSS
/* style.css */
body { font-size: 16px; }
p { font-weight: bold; }
span { color: red; }
p span { display: none; }
img { float: right; }
Render tree
Remember
- HTML is parsed incrementally
- CSS is parsed non-incrementally
- CSS is render blocking
- JavaScript is parser blocking
- Possible solution - async/defer
...
<script src="example.js" async></script>
<script src="example.js" defer></script>
...
Minimize
- Number of critical resources.
- Number of critical bytes.
- Critical path length.
Layout
Hello students
<p> (100%)
<img>
<div> (100%)
<html> <body> (viewport width=device-width)
Paint
Paint
Hello students
<p> (100%)
<img>
<div> (100%)
<html> <body> (viewport width=device-width)
Rasterizer draw calls:
- save
- clipRect
- save
- clipRect
- drawRect
- drawTextBlob
- drawTextBlob
- restore
- restore
- save
- clipRect
- save
- clipRect
- drawRect
- drawBitmapRectToRect
- restore
+ imageDecode + [Resize]
Composite Layers
Composite Layers
Composite Layers
App Lifecycles
RAIL / LIAR
App phases
Response | |
Animate | |
Idle | |
Load |
1000
50
16
100
= 1 s / 60 frames
=1s/60frames
ms
Typical Frame
CSS Animations
Web Animation API
Style
Layout
Paint
Composite
JavaScript
Tools and Examples
First measure then optimize
DEMO
Optimizations
JS, Styles and Layout, Paint and Composite
JavaScript
- Just In Time (JIT) Compiler
- requestAnimationFrame
- Web Workers + demo
JIT example
Function.prototype.toString()
V8
Micro-optimization
// Which is faster:
for (var i = 0; i < len; i++) ...
// or
while (++i < len) ...
// ?
// We don't know
requestAnimationFrame
function animate() {
// cool animation frame stuff
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Style
Layout
Paint
Composite
JavaScript
#1
function animate() {
// cool animation frame stuff
setTimeout(animate, 4);
}
animate();
requestAnimationFrame
Web Workers
HTML5
Worker
worker.js
main-script.js
postMessage(data)
postMessage(data)
onmessage
onmessage
var worker = new Worker('worker.js');
worker.onmessage = function(e) {
console.log('Worker said: ', e.data);
};
worker.postMessage('Hello World');
this.onmessage = function(e) {
// long-running JS goes here
postMessage(e.data);
};
Web Workers
DEMO
Limitations of Web Workers
- Start-up performance cost
- Memory cost for worker instance
- No access to DOM, window, navigator etc.
- Everything passed to a worker is copied entirely (exception - Transferrable Objects can be zero-copied)
JavaScript
- Avoid micro-optimizations.
- For visual updates use requestAnimaitonFrame rather than setTimeout/setInterval.
- Move long-running JavaScript to Web Workers.
Styles
- Reduce the complexity of your selectors.
- Use a class-centric methodology like BEM.
- Reduce the number of elements on which style calculation must be calculated.
Styles
<section class="post">
<h1 class="title">Egestas</h1>
<p class="description big">Pellentesque morbi turpis egestas.</p>
</section>
<section class="post">
<h1 class="post__title">Egestas</h1>
<p class="post__description--big">Pellentesque morbi turpis egestas.</p>
</section>
.post {}
.post .title {}
.post .description .big {}
.post {}
.post__title {}
.post__description--big {}
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
.item:nth-child(3) {
color: #0f0;
}
<div class="item"></div>
<div class="item"></div>
<div class="item third"></div>
.item.third {
color: #0f0;
}
<div class="item"></div>
<div class="item"></div>
<div class="item--third"></div>
.item--third {
color: #0f0;
}
Layout
- Use new flexboxes.
- Avoid triggering layout wherever possible.
- Avoid Forced Synchronous Layouts (FSL):
- for loops that force layout & change the DOM are the worst, avoid them.
- Batch your writes & reads to the DOM (via fastDOM or virtual DOM implementation).
requestAnimationFrame(logBoxHeight);
function logBoxHeight() {
box.classList.add('super-big');
console.log(box.offsetHeight);
}
requestAnimationFrame(logBoxHeight);
function logBoxHeight() {
console.log(box.offsetHeight);
box.classList.add('super-big');
}
Paint and Compositing
- Avoid paint where you can.
- Changing any property apart from transforms or opacity always triggers paint.
- Promote layers - reduce paint areas.
- Layout boundaries
- will-change and transform: translateZ(0)
- Promote elements that move or fade.
- Avoid overusing promotion; layers require memory and management.
DEMO
Thanks!
May the Forced syncronous layout NOT be with you!
Useful Links
Browser Rendering Optimization
By alex-kachura
Browser Rendering Optimization
Building 60 fps web apps
- 6,797