Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Performance on RAILs

Paul Lewis
November 23, 2015

Performance on RAILs

This is my talk from Nordic.js and BeyondConf.

Nordic.js Video: https://www.youtube.com/watch?v=uJMA2n4RL6s

Looking at web performance advice can be overwhelming: everything comes with caveats, disclaimers, and sometimes one piece of advice can seem to actively contradict another.

Phrases like “the DOM is slow” or “always use CSS animations!” make for great headlines, but the truth is often far more nuanced.

In this session we'll look at how to think holistically about performance, and how to prioritise optimisation work that your users will notice and appreciate.

Paul Lewis

November 23, 2015
Tweet

More Decks by Paul Lewis

Other Decks in Technology

Transcript

  1. Performance on
    RAILs

    View Slide

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

    View Slide

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

    View Slide

  4. too slow.
    @aerotwist

    View Slide

  5. is 50ms too slow?
    @aerotwist

    View Slide

  6. Page Load

    View Slide

  7. Scrolling

    View Slide

  8. WebGL Animation
    Cruciform by Jaume Sanchez Elias

    View Slide

  9. THE USER CONTEXT
    matters.
    @aerotwist

    View Slide

  10. @aerotwist

    View Slide

  11. View Slide

  12. View Slide

  13. How do we
    make performance
    user-focused?
    @aerotwist

    View Slide

  14. LET’S START WITH
    perception.
    @aerotwist

    View Slide

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

    View Slide

  16. 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

    View Slide

  17. 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

    View Slide

  18. 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

    View Slide

  19. 60fps
    @aerotwist

    View Slide

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

    View Slide

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

    View Slide

  22. View Slide

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

    View Slide

  24. R
    A
    I
    L
    ESPONSE
    NIMATION
    DLE
    OAD
    @aerotwist

    View Slide

  25. RAIL
    ESPONSE
    NIMATION
    DLE
    OAD
    @aerotwist

    View Slide

  26. Response.
    @aerotwist

    View Slide

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

    View Slide

  28. Animation.
    @aerotwist

    View Slide

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

    View Slide

  30. Idle.
    @aerotwist

    View Slide

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

    View Slide

  32. Load.
    @aerotwist

    View Slide

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

    View Slide

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

    View Slide

  35. TIME FOR A WORKED
    Example!
    @aerotwist

    View Slide

  36. voice-memos.appspot.com
    @aerotwist

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  41. View Slide

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

    View Slide

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

    View Slide

  44. // 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);
    }

    View Slide

  45. // 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);
    }

    View Slide

  46. // 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);
    }

    View Slide

  47. View Slide

  48. @aerotwist
    AND NOW, IT’S
    Combo time!

    View Slide

  49. View Slide

  50. View Slide

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

    View Slide

  52. csstriggers.com

    View Slide

  53. csstriggers.com

    View Slide

  54. Response Animation
    0.1s
    Response work
    Animation setup

    View Slide

  55. @aerotwist
    FLIP
    FIRST, LAST, INVERT, PLAY

    View Slide

  56. csstriggers.com

    View Slide

  57. csstriggers.com

    View Slide

  58. OPTION 2: FLIP
    getBoundingClientRect();

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  64. Response Animation
    0.1s
    Response work
    FLI P

    View Slide

  65. View Slide

  66. bit.ly/flip-anims

    View Slide

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

    View Slide

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

    View Slide

  69. 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

    View Slide

  70. View Slide

  71. View Slide

  72. View Slide

  73. View Slide

  74. View Slide

  75. View Slide

  76. 99%
    CONNECTION

    View Slide

  77. @aerotwist

    View Slide

  78. @aerotwist
    AND NOW REPEAT
    Loads…

    View Slide

  79. View Slide

  80. View Slide

  81. View Slide

  82. @aerotwist
    SERVICE WORKERS ARE
    awesome.

    View Slide

  83. @aerotwist

    View Slide

  84. @aerotwist

    View Slide

  85. @aerotwist

    View Slide

  86. @aerotwist

    View Slide

  87. @aerotwist

    View Slide

  88. @aerotwist

    View Slide

  89. @aerotwist

    View Slide

  90. @aerotwist

    View Slide

  91. @aerotwist

    View Slide

  92. @aerotwist

    View Slide

  93. Cache
    @aerotwist

    View Slide

  94. Cache
    @aerotwist

    View Slide

  95. @aerotwist

    View Slide

  96. Cache
    @aerotwist

    View Slide

  97. Cache
    @aerotwist

    View Slide

  98. 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...
    });
    );
    };

    View Slide

  99. 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...
    });
    );
    };

    View Slide

  100. 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...
    });
    );
    };

    View Slide

  101. 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...
    });
    );
    };

    View Slide

  102. 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...
    });
    );
    };

    View Slide

  103. 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;
    });

    View Slide

  104. 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;
    });

    View Slide

  105. 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;
    });

    View Slide

  106. 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;
    });

    View Slide

  107. 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;
    });

    View Slide

  108. 75TH PERCENTILE
    LOAD TIME < 1 SECOND
    @aerotwist

    View Slide

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

    View Slide

  110. @aerotwist
    WHATEVER HAPPENED TO
    Idle?

    View Slide

  111. requestIdleCallback
    @aerotwist

    View Slide

  112. 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);

    View Slide

  113. 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);

    View Slide

  114. 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);

    View Slide

  115. 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);

    View Slide

  116. 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);

    View Slide

  117. 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);

    View Slide

  118. bit.ly/requestidlecallback

    View Slide

  119. IDLE TIME IS
    different.
    @aerotwist

    View Slide

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

    View Slide

  121. SOME HANDY
    Links.
    @aerotwist

    View Slide

  122. 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

    View Slide

  123. feedback
    PLEASE SEND YOUR

    View Slide

  124. Thanks.

    View Slide