Slide 1

Slide 1 text

Performance on RAILs

Slide 2

Slide 2 text

SUCCESS PERFORMANCE “Fast is good, faster is better” - every bit of perf research Painfully slow user quits Bearable user persists Smooth user delight @aerotwist

Slide 3

Slide 3 text

SUCCESS PERFORMANCE Where is the happy middle? Smooth user delight Too slow Small ROI @aerotwist

Slide 4

Slide 4 text

too slow. @aerotwist

Slide 5

Slide 5 text

is 50ms too slow? @aerotwist

Slide 6

Slide 6 text

Page Load

Slide 7

Slide 7 text

Scrolling

Slide 8

Slide 8 text

WebGL Animation Cruciform by Jaume Sanchez Elias

Slide 9

Slide 9 text

THE USER CONTEXT matters. @aerotwist

Slide 10

Slide 10 text

@aerotwist

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

How do we make performance user-focused? @aerotwist

Slide 14

Slide 14 text

LET’S START WITH perception. @aerotwist

Slide 15

Slide 15 text

http://www.nngroup.com/articles/response-times-3-important-limits/ @aerotwist 0.1 SECONDS 1 SECOND 10 SECONDS

Slide 16

Slide 16 text

The user feels an instantaneous response. Any longer and the connection between action and reaction is broken. 0.1 SECONDS http://www.nngroup.com/articles/response-times-3-important-limits/ @aerotwist

Slide 17

Slide 17 text

The user feels an instantaneous response. Any longer and the connection between action and reaction is broken. 0.1 SECONDS The user’s flow of thought is seamless. Beyond it, the user loses focus and attention. 1 SECOND http://www.nngroup.com/articles/response-times-3-important-limits/ @aerotwist

Slide 18

Slide 18 text

The user feels an instantaneous response. Any longer and the connection between action and reaction is broken. 0.1 SECONDS The user’s flow of thought is seamless. Beyond it, the user loses focus and attention. 1 SECOND You’ve lost the user’s attention. 10 SECONDS http://www.nngroup.com/articles/response-times-3-important-limits/ @aerotwist

Slide 19

Slide 19 text

60fps @aerotwist

Slide 20

Slide 20 text

http://www.nngroup.com/articles/response-times-3-important-limits/ @aerotwist 0.1 SECONDS 1 SECOND 10 SECONDS 16 MILLISECONDS

Slide 21

Slide 21 text

NOW LET’S LOOK AT A real website. @aerotwist

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

LET’S TIE THE TWO together. (perception, meet reality) @aerotwist

Slide 24

Slide 24 text

R A I L ESPONSE NIMATION DLE OAD @aerotwist

Slide 25

Slide 25 text

RAIL ESPONSE NIMATION DLE OAD @aerotwist

Slide 26

Slide 26 text

Response. @aerotwist

Slide 27

Slide 27 text

The user feels an instantaneous response. Any longer and the connection between action and reaction is broken. 0.1 SECONDS @aerotwist

Slide 28

Slide 28 text

Animation. @aerotwist

Slide 29

Slide 29 text

Visual changes feel smooth and consistent. 16 MILLISECONDS Any variation in frame rate will be disconcerting to the user. @aerotwist

Slide 30

Slide 30 text

Idle. @aerotwist

Slide 31

Slide 31 text

Allows for responding to user interaction. Any longer and we may not be responsive. 50 MILLISECONDS @aerotwist

Slide 32

Slide 32 text

Load. @aerotwist

Slide 33

Slide 33 text

The user’s flow of thought is seamless. Beyond it, the user loses focus and attention. 1 SECOND @aerotwist

Slide 34

Slide 34 text

http://www.nngroup.com/articles/response-times-3-important-limits/ 0.1 SECONDS 1 SECOND 16 MILLISECONDS 50 MILLISECONDS RESPONSE ANIMATION IDLE LOAD @aerotwist

Slide 35

Slide 35 text

TIME FOR A WORKED Example! @aerotwist

Slide 36

Slide 36 text

voice-memos.appspot.com @aerotwist

Slide 37

Slide 37 text

@aerotwist MEASURE & OPTIMIZE Response. (our target: 100ms)

Slide 38

Slide 38 text

// Response: 100ms. this.sideNavToggleButton.addEventListener('click', () => { console.time('side-nav-tap'); this.toggleSideNav(); // Wait until the frame ships. requestAnimationFrame( _ => { console.timeEnd('side-nav-tap'); }); } );

Slide 39

Slide 39 text

// Response: 100ms. this.sideNavToggleButton.addEventListener('click', () => { console.time('side-nav-tap'); this.toggleSideNav(); // Wait until the frame ships. requestAnimationFrame( _ => { console.timeEnd('side-nav-tap'); }); } );

Slide 40

Slide 40 text

// Response: 100ms. this.sideNavToggleButton.addEventListener('click', () => { console.time('side-nav-tap'); this.toggleSideNav(); // Wait until the frame ships. requestAnimationFrame( _ => { console.timeEnd('side-nav-tap'); }); } );

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

Simple responses are usually well under 100ms. Use console.time / timeEnd to track response times. RESPONSES @aerotwist

Slide 43

Slide 43 text

MEASURE & OPTIMIZE Animation. (our target: 8ms) @aerotwist

Slide 44

Slide 44 text

// Animation: 60fps / 16ms per frame. showDetailsView () { console.time('heroTransitionShow'); this.heroImage.classList.add('hero-image--visible'); let onHeroAnimationFinished = () => { console.timeEnd('heroTransitionShow'); this.heroImage.removeEventListener('transitionend', onSideNavOpen); } // Listen for the end of the animation. this.heroImage.addEventListener(‘transitionend', onSideNavOpen); }

Slide 45

Slide 45 text

// Animation: 60fps / 16ms per frame. showDetailsView () { console.time('heroTransitionShow'); this.heroImage.classList.add('hero-image--visible'); let onHeroAnimationFinished = () => { console.timeEnd('heroTransitionShow'); this.heroImage.removeEventListener('transitionend', onSideNavOpen); } // Listen for the end of the animation. this.heroImage.addEventListener(‘transitionend', onSideNavOpen); }

Slide 46

Slide 46 text

// Animation: 60fps / 16ms per frame. showDetailsView () { console.time('heroTransitionShow'); this.heroImage.classList.add('hero-image--visible'); let onHeroAnimationFinished = () => { console.timeEnd('heroTransitionShow'); this.heroImage.removeEventListener('transitionend', onSideNavOpen); } // Listen for the end of the animation. this.heroImage.addEventListener(‘transitionend', onSideNavOpen); }

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

@aerotwist AND NOW, IT’S Combo time!

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

OPTION 1: NATURAL transition: width, height, left, top;

Slide 52

Slide 52 text

csstriggers.com

Slide 53

Slide 53 text

csstriggers.com

Slide 54

Slide 54 text

Response Animation 0.1s Response work Animation setup

Slide 55

Slide 55 text

@aerotwist FLIP FIRST, LAST, INVERT, PLAY

Slide 56

Slide 56 text

csstriggers.com

Slide 57

Slide 57 text

csstriggers.com

Slide 58

Slide 58 text

OPTION 2: FLIP getBoundingClientRect();

Slide 59

Slide 59 text

OPTION 2: FLIP getBoundingClientRect(); el.classList.add(‘expanded’);

Slide 60

Slide 60 text

OPTION 2: FLIP getBoundingClientRect(); el.classList.add(‘expanded’);

Slide 61

Slide 61 text

OPTION 2: FLIP getBoundingClientRect(); el.classList.add(‘expanded’); transform: translate(-321px, -89px) scale(0.32);

Slide 62

Slide 62 text

OPTION 2: FLIP getBoundingClientRect(); el.classList.add(‘expanded’); transform: translate(-321px, -89px) scale(0.32); transition: transform;

Slide 63

Slide 63 text

OPTION 2: FLIP getBoundingClientRect(); el.classList.add(‘expanded’); transform: translate(-321px, -89px) scale(0.32); transform: none; transition: transform;

Slide 64

Slide 64 text

Response Animation 0.1s Response work FLI P

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

bit.ly/flip-anims

Slide 67

Slide 67 text

Animate with transforms and opacity where you can. FLIP expensive properties when coupled with Responses. ANIMATIONS Be super cautious; avoid work during scrolls. @aerotwist

Slide 68

Slide 68 text

@aerotwist MEASURE & OPTIMIZE Load. (our target: 1,000ms)

Slide 69

Slide 69 text

voicememos-core.css INLINED record-something.svg 1 2 voicememos-record.js voicememos-record.css voicememos-core.js 3 voicememos-edit.js voicememos-edit.css 4 voicememos-list.js voicememos-list.css 5 voicememos-details.js voicememos-details.css

Slide 70

Slide 70 text

No content

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

No content

Slide 73

Slide 73 text

No content

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

99% CONNECTION

Slide 77

Slide 77 text

@aerotwist

Slide 78

Slide 78 text

@aerotwist AND NOW REPEAT Loads…

Slide 79

Slide 79 text

No content

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

@aerotwist SERVICE WORKERS ARE awesome.

Slide 83

Slide 83 text

@aerotwist

Slide 84

Slide 84 text

@aerotwist

Slide 85

Slide 85 text

@aerotwist

Slide 86

Slide 86 text

@aerotwist

Slide 87

Slide 87 text

@aerotwist

Slide 88

Slide 88 text

@aerotwist

Slide 89

Slide 89 text

@aerotwist

Slide 90

Slide 90 text

@aerotwist

Slide 91

Slide 91 text

@aerotwist

Slide 92

Slide 92 text

@aerotwist

Slide 93

Slide 93 text

Cache @aerotwist

Slide 94

Slide 94 text

Cache @aerotwist

Slide 95

Slide 95 text

@aerotwist

Slide 96

Slide 96 text

Cache @aerotwist

Slide 97

Slide 97 text

Cache @aerotwist

Slide 98

Slide 98 text

self.onfetch = function(event) { event.respondWith( // Check the cache... caches.match(request).then( function(response) { // Return it if you got it. if (response) return response; // Otherwise... }); ); };

Slide 99

Slide 99 text

self.onfetch = function(event) { event.respondWith( // Check the cache... caches.match(request).then( function(response) { // Return it if you got it. if (response) return response; // Otherwise... }); ); };

Slide 100

Slide 100 text

self.onfetch = function(event) { event.respondWith( // Check the cache... caches.match(request).then( function(response) { // Return it if you got it. if (response) return response; // Otherwise... }); ); };

Slide 101

Slide 101 text

self.onfetch = function(event) { event.respondWith( // Check the cache... caches.match(request).then( function(response) { // Return it if you got it. if (response) return response; // Otherwise... }); ); };

Slide 102

Slide 102 text

self.onfetch = function(event) { event.respondWith( // Check the cache... caches.match(request).then( function(response) { // Return it if you got it. if (response) return response; // Otherwise... }); ); };

Slide 103

Slide 103 text

return fetch(request).then(function(response) { // Anything not A-OK or opaque (CORSy stuff) we return. if (response.status !== 200 && response.status !== 0) return response; // Clone and cache the request and response. var responseToCache = response.clone(); var requestToCache = request.clone(); caches.open('le-cache').then(function(cache) { cache.put(requestToCache, responseToCache).catch(function(err) { console.warn(err.message); }); }); return response; });

Slide 104

Slide 104 text

return fetch(request).then(function(response) { // Anything not A-OK or opaque (CORSy stuff) we return. if (response.status !== 200 && response.status !== 0) return response; // Clone and cache the request and response. var responseToCache = response.clone(); var requestToCache = request.clone(); caches.open('le-cache').then(function(cache) { cache.put(requestToCache, responseToCache).catch(function(err) { console.warn(err.message); }); }); return response; });

Slide 105

Slide 105 text

return fetch(request).then(function(response) { // Anything not A-OK or opaque (CORSy stuff) we return. if (response.status !== 200 && response.status !== 0) return response; // Clone and cache the request and response. var responseToCache = response.clone(); var requestToCache = request.clone(); caches.open('le-cache').then(function(cache) { cache.put(requestToCache, responseToCache).catch(function(err) { console.warn(err.message); }); }); return response; });

Slide 106

Slide 106 text

return fetch(request).then(function(response) { // Anything not A-OK or opaque (CORSy stuff) we return. if (response.status !== 200 && response.status !== 0) return response; // Clone and cache the request and response. var responseToCache = response.clone(); var requestToCache = request.clone(); caches.open('le-cache').then(function(cache) { cache.put(requestToCache, responseToCache).catch(function(err) { console.warn(err.message); }); }); return response; });

Slide 107

Slide 107 text

return fetch(request).then(function(response) { // Anything not A-OK or opaque (CORSy stuff) we return. if (response.status !== 200 && response.status !== 0) return response; // Clone and cache the request and response. var responseToCache = response.clone(); var requestToCache = request.clone(); caches.open('le-cache').then(function(cache) { cache.put(requestToCache, responseToCache).catch(function(err) { console.warn(err.message); }); }); return response; });

Slide 108

Slide 108 text

75TH PERCENTILE LOAD TIME < 1 SECOND @aerotwist

Slide 109

Slide 109 text

Inline app shell CSS. Lazy-load (async, defer) everything else. Far-future cache JS, CSS, images etc. LOAD Use a Service Worker. @aerotwist

Slide 110

Slide 110 text

@aerotwist WHATEVER HAPPENED TO Idle?

Slide 111

Slide 111 text

requestIdleCallback @aerotwist

Slide 112

Slide 112 text

var tasks = []; var MIN_TASK_TIME = 1; function handleTasks(deadline) { while (deadline.timeRemaining() > MIN_TASK_TIME && tasks.length) processTask(tasks.pop()); if (tasks.length) requestIdleCallback(handleTasks); } requestIdleCallback(handleTasks);

Slide 113

Slide 113 text

var tasks = []; var MIN_TASK_TIME = 1; function handleTasks(deadline) { while (deadline.timeRemaining() > MIN_TASK_TIME && tasks.length) processTask(tasks.pop()); if (tasks.length) requestIdleCallback(handleTasks); } requestIdleCallback(handleTasks);

Slide 114

Slide 114 text

var tasks = []; var MIN_TASK_TIME = 1; function handleTasks(deadline) { while (deadline.timeRemaining() > MIN_TASK_TIME && tasks.length) processTask(tasks.pop()); if (tasks.length) requestIdleCallback(handleTasks); } requestIdleCallback(handleTasks);

Slide 115

Slide 115 text

var tasks = []; var MIN_TASK_TIME = 1; function handleTasks(deadline) { while (deadline.timeRemaining() > MIN_TASK_TIME && tasks.length) processTask(tasks.pop()); if (tasks.length) requestIdleCallback(handleTasks); } requestIdleCallback(handleTasks);

Slide 116

Slide 116 text

var tasks = []; var MIN_TASK_TIME = 1; function handleTasks(deadline) { while (deadline.timeRemaining() > MIN_TASK_TIME && tasks.length) processTask(tasks.pop()); if (tasks.length) requestIdleCallback(handleTasks); } requestIdleCallback(handleTasks);

Slide 117

Slide 117 text

var tasks = []; var MIN_TASK_TIME = 1; function handleTasks(deadline) { while (deadline.timeRemaining() > MIN_TASK_TIME && tasks.length) processTask(tasks.pop()); if (tasks.length) requestIdleCallback(handleTasks); } requestIdleCallback(handleTasks);

Slide 118

Slide 118 text

bit.ly/requestidlecallback

Slide 119

Slide 119 text

IDLE TIME IS different. @aerotwist

Slide 120

Slide 120 text

Use it for any non-essential work e.g. analytics. Think in small, deterministic operations. IDLE Think defensively. @aerotwist

Slide 121

Slide 121 text

SOME HANDY Links. @aerotwist

Slide 122

Slide 122 text

Udacity: Browser Rendering Optimization bit.ly/rail-udacity Web Fundamentals: Performance bit.ly/fundamentals-perf Voice Memos voice-memos.appspot.com aerotwist.com/blog/voice-memos/ Udacity: Critical Rendering Path bit.ly/crp-udacity @aerotwist

Slide 123

Slide 123 text

feedback PLEASE SEND YOUR

Slide 124

Slide 124 text

Thanks.