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 full-size 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 full-size slide

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

    View full-size slide

  4. too slow.
    @aerotwist

    View full-size slide

  5. is 50ms too slow?
    @aerotwist

    View full-size slide

  6. WebGL Animation
    Cruciform by Jaume Sanchez Elias

    View full-size slide

  7. THE USER CONTEXT
    matters.
    @aerotwist

    View full-size slide

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

    View full-size slide

  9. LET’S START WITH
    perception.
    @aerotwist

    View full-size slide

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

    View full-size slide

  11. 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 full-size slide

  12. 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 full-size slide

  13. 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 full-size slide

  14. 60fps
    @aerotwist

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  19. RAIL
    ESPONSE
    NIMATION
    DLE
    OAD
    @aerotwist

    View full-size slide

  20. Response.
    @aerotwist

    View full-size slide

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

    View full-size slide

  22. Animation.
    @aerotwist

    View full-size slide

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

    View full-size slide

  24. Idle.
    @aerotwist

    View full-size slide

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

    View full-size slide

  26. Load.
    @aerotwist

    View full-size slide

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

    View full-size slide

  28. 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 full-size slide

  29. TIME FOR A WORKED
    Example!
    @aerotwist

    View full-size slide

  30. voice-memos.appspot.com
    @aerotwist

    View full-size slide

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

    View full-size slide

  32. // 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 full-size slide

  33. // 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 full-size slide

  34. // 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  37. // 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 full-size slide

  38. // 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 full-size slide

  39. // 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  42. csstriggers.com

    View full-size slide

  43. csstriggers.com

    View full-size slide

  44. Response Animation
    0.1s
    Response work
    Animation setup

    View full-size slide

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

    View full-size slide

  46. csstriggers.com

    View full-size slide

  47. csstriggers.com

    View full-size slide

  48. OPTION 2: FLIP
    getBoundingClientRect();

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  54. Response Animation
    0.1s
    Response work
    FLI P

    View full-size slide

  55. bit.ly/flip-anims

    View full-size slide

  56. 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 full-size slide

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

    View full-size slide

  58. 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 full-size slide

  59. 99%
    CONNECTION

    View full-size slide

  60. @aerotwist
    AND NOW REPEAT
    Loads…

    View full-size slide

  61. @aerotwist
    SERVICE WORKERS ARE
    awesome.

    View full-size slide

  62. Cache
    @aerotwist

    View full-size slide

  63. Cache
    @aerotwist

    View full-size slide

  64. Cache
    @aerotwist

    View full-size slide

  65. Cache
    @aerotwist

    View full-size slide

  66. 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 full-size slide

  67. 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 full-size slide

  68. 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 full-size slide

  69. 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 full-size slide

  70. 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 full-size slide

  71. 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 full-size slide

  72. 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 full-size slide

  73. 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 full-size slide

  74. 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 full-size slide

  75. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  78. @aerotwist
    WHATEVER HAPPENED TO
    Idle?

    View full-size slide

  79. requestIdleCallback
    @aerotwist

    View full-size slide

  80. 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 full-size slide

  81. 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 full-size slide

  82. 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 full-size slide

  83. 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 full-size slide

  84. 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 full-size slide

  85. 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 full-size slide

  86. bit.ly/requestidlecallback

    View full-size slide

  87. IDLE TIME IS
    different.
    @aerotwist

    View full-size slide

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

    View full-size slide

  89. SOME HANDY
    Links.
    @aerotwist

    View full-size slide

  90. 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 full-size slide

  91. feedback
    PLEASE SEND YOUR

    View full-size slide