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

Watch your back, Browser! You're being observed.

Watch your back, Browser! You're being observed.

stefan judis

May 23, 2017
Tweet

More Decks by stefan judis

Other Decks in Technology

Transcript

  1. Watch your back,
    Browser!
    You're being observed.
    @stefanjudis

    View full-size slide

  2. Stefan Judis
    Frontend Developer, Occasional Teacher, Meetup Organizer
    ❤ Open Source, Performance and Accessibility ❤
    @stefanjudis

    View full-size slide

  3. I'm excited about
    conferences and meetups

    View full-size slide

  4. The web platform evolves
    really fast these days

    View full-size slide

  5. Staying up to date is
    part of our job

    View full-size slide

  6. JavaScript Ecosystem

    View full-size slide

  7. Browser APIs adapt
    common use cases

    View full-size slide

  8. Browser APIs adapt
    common use cases
    pull

    View full-size slide

  9. Browser APIs adapt
    common use cases
    pull push

    View full-size slide

  10. Let's celebrate!

    View full-size slide

  11. codepen.io/thebabydino/pen/RRRRZE

    View full-size slide

  12. codepen.io/thebabydino/pen/RRRRZE

    View full-size slide

  13. function isElementInViewport (el) {
    var rect = el.getBoundingClientRect();
    return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
    }
    How to tell if a DOM element
    is visible in the current viewport?
    stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport

    View full-size slide

  14. function isElementInViewport (el) {
    var rect = el.getBoundingClientRect(); // can trigger force layout/reflow
    return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
    }
    How to tell if a DOM element
    is visible in the current viewport?
    stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport

    View full-size slide

  15. WHAT FORCES
    LAYOUT / REFLOW
    gist.github.com/paulirish/5d52fb081b3570c81e3a

    View full-size slide

  16. Implementation with 'scroll'
    const speakers = [...document.querySelectorAll('.speaker-details')];
    window.addEventListener('scroll', () => {
    speakers.forEach(elem => {
    if (isElementInViewport(elem)) {
    elem.classList.add('party-party');
    speakers.splice(speakers.indexOf(elem), 1);
    }
    });
    });

    View full-size slide

  17. Implementation with 'scroll'
    const speakers = [...document.querySelectorAll('.speaker-details')];
    window.addEventListener('scroll', () => { // really expensive
    speakers.forEach(elem => {
    if (isElementInViewport(elem)) {
    elem.classList.add('party-party');
    speakers.splice(speakers.indexOf(elem), 1);
    }
    });
    });

    View full-size slide

  18. Implementation with 'scroll'
    60FPS
    1s / 60
    16ms

    View full-size slide

  19. Implementation with 'scroll'
    60FPS
    1s / 60
    10ms
    16ms

    View full-size slide

  20. Intersection Observer

    View full-size slide

  21. - Intersection Observer -
    (function() {
    const options = {
    threshold: 1.0
    };
    const intersectionObserver = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
    if (entry.isIntersecting) {
    entry.target.classList.add('party-party');
    intersectionObserver.unobserve(entry.target);
    }
    });
    }, options);
    [...document.querySelectorAll('.speaker-details')]
    .forEach(elem => intersectionObserver.observe(elem));
    })();

    View full-size slide

  22. - Intersection Observer -
    (function() {
    const options = {
    threshold: 1.0
    };
    const intersectionObserver = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
    if (entry.isIntersecting) {
    entry.target.classList.add('party-party');
    intersectionObserver.unobserve(entry.target);
    }
    });
    }, options);
    [...document.querySelectorAll('.speaker-details')]
    .forEach(elem => intersectionObserver.observe(elem));
    })();

    View full-size slide

  23. - Intersection Observer -
    (function() {
    const options = {
    threshold: 1.0
    };
    const intersectionObserver = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
    if (entry.isIntersecting) {
    entry.target.classList.add('party-party');
    intersectionObserver.unobserve(entry.target);
    }
    });
    }, options);
    [...document.querySelectorAll('.speaker-details')]
    .forEach(elem => intersectionObserver.observe(elem));
    })();

    View full-size slide

  24. (function() {
    const options = {
    threshold: 1.0
    };
    const intersectionObserver = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
    if (entry.isIntersecting) {
    entry.target.classList.add('party-party');
    intersectionObserver.unobserve(entry.target);
    }
    });
    }, options);
    [...document.querySelectorAll('.speaker-details')]
    .forEach(elem => intersectionObserver.observe(elem));
    })();
    - Intersection Observer -

    View full-size slide

  25. - Intersection Observer -
    const options = {
    root: null,
    // ☝ or document.querySelector('.foo')
    // default: null
    rootMargin: '10px',
    // ☝ or '10% 5%' or '1px 10% 3px 12%'
    // default: '0px 0px 0px 0px'
    threshold: 0.5
    // ☝ or [0.25, 0.5, 0.75, 1]
    // default: 0
    };
    w3c.github.io/IntersectionObserver/#intersection-observer-interface

    View full-size slide

  26. - Intersection Observer -
    const options = {
    root: null,
    // ☝ or document.querySelector('.foo')
    // default: null
    rootMargin: '10px',
    // ☝ or '10% 5%' or '1px 10% 3px 12%'
    // default: '0px 0px 0px 0px'
    threshold: 0.5
    // ☝ or [0.25, 0.5, 0.75, 1]
    // default: 0
    };
    w3c.github.io/IntersectionObserver/#intersection-observer-interface

    View full-size slide

  27. - Intersection Observer -
    const options = {
    root: null,
    // ☝ or document.querySelector('.foo')
    // default: null
    rootMargin: '10px',
    // ☝ or '10% 5%' or '1px 10% 3px 12%'
    // default: '0px 0px 0px 0px'
    threshold: 0.5
    // ☝ or [0.25, 0.5, 0.75, 1]
    // default: 0
    };
    w3c.github.io/IntersectionObserver/#intersection-observer-interface

    View full-size slide

  28. // this fires when:
    // 1. The target begins entering the viewport (0 < ratio < 1).
    // 2. The target fully enters the viewport (ratio >= 1).
    // 3. The target begins leaving the viewport (1 > ratio > 0).
    // 4. The target fully leaves the viewport (ratio <= 0).
    let observer = new IntersectionObserver(handler, {
    threshold: [0, 1]
    });
    - Intersection Observer -
    hacks.mozilla.org/2017/08/intersection-observer-comes-to-firefox/

    View full-size slide

  29. - Intersection Observer -
    threshold = 0
    viewport

    View full-size slide

  30. - Intersection Observer -
    threshold = 0
    entry => {
    console.log(entry.isIntersecting)
    // true
    console.log(entry.intersectionRatio)
    // sth. around 0
    }
    viewport

    View full-size slide

  31. - Intersection Observer -
    threshold = 0
    entry => {
    console.log(entry.isIntersecting)
    // false
    console.log(entry.intersectionRatio)
    // 0
    }
    viewport

    View full-size slide

  32. - Intersection Observer -
    threshold > 0
    (e.g. 0.5)
    viewport

    View full-size slide

  33. - Intersection Observer -
    threshold > 0
    (e.g. 0.5)
    entry => {
    console.log(entry.isIntersecting)
    // true
    console.log(entry.intersectionRatio)
    // sth. around 0.5
    }
    viewport

    View full-size slide

  34. - Intersection Observer -
    threshold > 0
    (e.g. 0.5)
    entry => {
    console.log(entry.isIntersecting)
    // true
    console.log(entry.intersectionRatio)
    // sth. around 0.5
    }
    viewport

    View full-size slide

  35. - Intersection Observer -
    developers.google.com/web/fundamentals/media/mobile-web-video-playback
    if ('IntersectionObserver' in window) {
    // Show/hide mute button based
    // on video visibility in the page.
    function onIntersection(entries) {
    entries.forEach(function(entry) {
    muteButton.hidden = video.paused || entry.isIntersecting;
    });
    }
    var observer = new IntersectionObserver(onIntersection);
    observer.observe(video);
    }

    View full-size slide

  36. - Intersection Observer -
    developers.google.com/web/fundamentals/media/mobile-web-video-playback
    if ('IntersectionObserver' in window) {
    // Show/hide mute button based
    // on video visibility in the page.
    function onIntersection(entries) {
    entries.forEach(function(entry) {
    muteButton.hidden = video.paused || entry.isIntersecting;
    });
    }
    var observer = new IntersectionObserver(onIntersection);
    observer.observe(video);
    }

    View full-size slide

  37. twitter.com/jaffathecake/status/857537625154097152

    View full-size slide

  38. twitter.com/jaffathecake/status/857537625154097152

    View full-size slide

  39. - Intersection Observer -
    caniuse.com/#feat=intersectionobserver
    * in development
    *

    View full-size slide

  40. - Intersection Observer -
    *
    Incompatibilities
    between spec and implementations
    github.com/WICG/IntersectionObserver/issues/222
    github.com/WICG/IntersectionObserver/issues/211

    View full-size slide

  41. - Intersection Observer -
    *
    Incompatibilities
    between spec and implementations
    github.com/WICG/IntersectionObserver/issues/222
    github.com/WICG/IntersectionObserver/issues/211
    Resolved since
    Oct 17, 2017

    View full-size slide

  42. - Intersection Observer -
    Polyfillable?
    github.com/WICG/IntersectionObserver
    YES!

    View full-size slide

  43. Let's celebrate the
    whole schedule!

    View full-size slide

  44. - Intersection Observer -
    (function() {
    // Intersection Observer code
    // third-party implementation
    // No interface to hook into
    // No access to the implementation
    // (ಥ_ಥ) (ಥ_ಥ) (ಥ_ಥ)
    })();

    View full-size slide

  45. Mutation Observer

    View full-size slide

  46. - Mutation Observer -
    (function() {
    const list = document.querySelector('.schedule');
    const nrOfTalks = list.querySelectorAll('.speaker').length;
    const config = { attributes: true, subtree: true };
    let talksSeen = 0;
    const mutationObserver = new MutationObserver(mutations => {
    mutations.forEach((mutation) => {
    if (
    mutation.type === 'attributes' &&
    mutation.target.classList.contains('party-party')
    ) {
    talksSeen++;
    }
    })
    if (talksSeen === nrOfTalks) {
    [...Array(20)].forEach(cornify_add);
    mutationObserver.disconnect();
    }
    });
    mutationObserver.observe(list, config);
    })();

    View full-size slide

  47. (function() {
    const list = document.querySelector('.schedule');
    const nrOfTalks = list.querySelectorAll('.speaker').length;
    const config = { attributes: true, subtree: true };
    let talksSeen = 0;
    const mutationObserver = new MutationObserver(mutations => {
    mutations.forEach((mutation) => {
    if (
    mutation.type === 'attributes' &&
    mutation.target.classList.contains('party-party')
    ) {
    talksSeen++;
    }
    })
    if (talksSeen === nrOfTalks) {
    [...Array(20)].forEach(cornify_add);
    mutationObserver.disconnect();
    }
    });
    mutationObserver.observe(list, config);
    })();
    - Mutation Observer -

    View full-size slide

  48. (function() {
    const list = document.querySelector('.schedule');
    const nrOfTalks = list.querySelectorAll('.speaker').length;
    const config = { attributes: true, subtree: true };
    let talksSeen = 0;
    const mutationObserver = new MutationObserver(mutations => {
    mutations.forEach((mutation) => {
    if (
    mutation.type === 'attributes' &&
    mutation.target.classList.contains('party-party')
    ) {
    talksSeen++;
    }
    })
    if (talksSeen === nrOfTalks) {
    [...Array(20)].forEach(cornify_add);
    mutationObserver.disconnect();
    }
    });
    mutationObserver.observe(list, config);
    })();
    - Mutation Observer -

    View full-size slide

  49. (function() {
    const list = document.querySelector('.schedule');
    const nrOfTalks = list.querySelectorAll('.speaker').length;
    const config = { attributes: true, subtree: true };
    let talksSeen = 0;
    const mutationObserver = new MutationObserver(mutations => {
    mutations.forEach((mutation) => {
    if (
    mutation.type === 'attributes' &&
    mutation.target.classList.contains('party-party')
    ) {
    talksSeen++;
    }
    })
    if (talksSeen === nrOfTalks) {
    [...Array(20)].forEach(cornify_add);
    mutationObserver.disconnect();
    }
    });
    mutationObserver.observe(list, config);
    })();
    - Mutation Observer -

    View full-size slide

  50. const options = {
    childList: true,
    // ☝ observe target's children
    attributes: true,
    // ☝ observe target's attributes
    subtree: true,
    // ☝ observe target and its descendants
    ...
    };
    dom.spec.whatwg.org/#interface-mutationobserver
    - Mutation Observer -

    View full-size slide

  51. const options = {
    childList: true,
    // ☝ observe target's children
    attributes: true,
    // ☝ observe target's attributes
    subtree: true,
    // ☝ observe target and its descendants
    ...
    };
    dom.spec.whatwg.org/#interface-mutationobserver
    - Mutation Observer -

    View full-size slide

  52. const options = {
    childList: true,
    // ☝ observe target's children
    attributes: true,
    // ☝ observe target's attributes
    subtree: true,
    // ☝ observe target and its descendants
    ...
    };
    dom.spec.whatwg.org/#interface-mutationobserver
    - Mutation Observer -

    View full-size slide

  53. const options = {
    childList: true,
    // ☝ observe target's children
    attributes: true,
    // ☝ observe target's attributes
    subtree: true,
    // ☝ observe target and its descendants
    ...
    };
    dom.spec.whatwg.org/#interface-mutationobserver
    - Mutation Observer -

    View full-size slide

  54. caniuse.com/#feat=mutationobserver
    - Mutation Observer -

    View full-size slide

  55. But let's always
    watch out
    for performance

    View full-size slide

  56. WAYS TO MEASURE PERFORMANCE
    Synthetic
    Monitoring
    Real User
    Monitoring

    View full-size slide

  57. REAL USER METRICS (RUM)
    NAVIGATION
    TIMING API
    RESOURCE
    TIMING API
    USER
    TIMING API

    View full-size slide

  58. - Navigation Timing API -
    w3c.github.io/navigation-timing/
    window.performance.timing

    View full-size slide

  59. - Navigation Timing API -
    {
    "navigationStart": 1494722965671,
    "unloadEventStart": 0,
    "unloadEventEnd": 0,
    "redirectStart": 0,
    "redirectEnd": 0,
    "fetchStart": 1494722965838,
    "domainLookupStart": 1494722965841,
    "domainLookupEnd": 1494722972627,
    "connectStart": 1494722972627,
    "connectEnd": 1494722973191,
    "secureConnectionStart": 1494722972815,
    "requestStart": 1494722973191,
    "responseStart": 1494722973667,
    "responseEnd": 1494722973681,
    "domLoading": 1494722973681,
    "domInteractive": 1494722974288,
    "domContentLoadedEventStart": 1494722974288,
    "domContentLoadedEventEnd": 1494722974320,
    "domComplete": 1494722974571,
    "loadEventStart": 1494722974571,
    "loadEventEnd": 1494722974574
    }
    w3c.github.io/navigation-timing/
    window.performance.timing

    View full-size slide

  60. - Navigation Timing API -
    w3c.github.io/navigation-timing/

    View full-size slide

  61. - Resource Timing API -
    window.performance.getEntriesByType('resource')

    View full-size slide

  62. - Resource Timing API -
    [
    ...,
    {
    connectEnd: 117.69500000000001,
    connectStart: 117.69500000000001,
    decodedBodySize: 20133,
    domainLookupEnd: 117.69500000000001,
    domainLookupStart: 117.69500000000001,
    duration: 846.3100000000001,
    encodedBodySize: 20133,
    entryType: 'resource',
    fetchStart: 117.69500000000001,
    initiatorType: 'img',
    name: 'http://127.0.0.1:8080/image.png',
    redirectEnd: 0,
    redirectStart: 0,
    requestStart: 962.6750000000001,
    responseEnd: 964.0050000000001,
    responseStart: 963.45,
    secureConnectionStart: 0,
    startTime: 117.69500000000001,
    transferSize: 20391,
    workerStart: 0
    }
    ]
    www.w3.org/TR/resource-timing-1/
    window.performance.getEntriesByType('resource')

    View full-size slide

  63. [...Array(20)].forEach(cornify_add);
    - User Timing API -
    w3c.github.io/user-timing/
    if (talksSeen === nrOfTalks) {
    }

    View full-size slide

  64. [...Array(20)].forEach(cornify_add);
    - User Timing API -
    w3c.github.io/user-timing/
    if (talksSeen === nrOfTalks) {
    }

    View full-size slide

  65. - User Timing API -
    w3c.github.io/user-timing/
    if (talksSeen === nrOfTalks) {
    // measure how long this takes
    performance.mark('cornify_start');
    [...Array(20)].forEach(cornify_add);
    performance.mark('cornify_end');
    performance.measure(
    'cornify_processing_time',
    'cornify_start',
    'cornify_end'
    );
    }

    View full-size slide

  66. - User Timing API -
    w3c.github.io/user-timing/
    window.performance.getEntriesByType('mark')
    [
    {
    duration: 0
    entryType: 'mark'
    name: 'cornify_start'
    startTime: 39613.885
    },
    ...
    ]

    View full-size slide

  67. - User Timing API -
    w3c.github.io/user-timing/
    window.performance.getEntriesByType('measure')
    [
    {
    duration: 5.9900000000016
    entryType: 'measure'
    name: 'cornify_processing_time'
    startTime: 46002.34500000001
    },
    ...
    ]

    View full-size slide

  68. ENTRY TYPE VALUES
    mark measure navigation resource
    ACCESSIBLE VIA
    getEntries getEntriesByType getEntriesByName

    View full-size slide

  69. Performance Observer

    View full-size slide

  70. - Performance Observer -
    (function() {
    const perfObserver = new PerformanceObserver(list => {
    list.getEntries().forEach((entry) => {
    console.log(
    `Name: ${ entry.name }, Duration: ${ entry.duration }`
    );
    });
    });
    perfObserver.observe({entryTypes: ['measure']});
    })();
    www.w3.org/TR/performance-timeline-2/#dom-performanceobserver

    View full-size slide

  71. (function() {
    const perfObserver = new PerformanceObserver(list => {
    list.getEntries().forEach((entry) => {
    console.log(
    `Name: ${ entry.name }, Duration: ${ entry.duration }`
    );
    });
    });
    perfObserver.observe({entryTypes: ['measure']});
    })();
    www.w3.org/TR/performance-timeline-2/#dom-performanceobserver
    - Performance Observer -

    View full-size slide

  72. (function() {
    const perfObserver = new PerformanceObserver(list => {
    list.getEntries().forEach((entry) => {
    console.log(
    `Name: ${ entry.name }, Duration: ${ entry.duration }`
    );
    });
    });
    perfObserver.observe({entryTypes: ['measure']});
    })();
    www.w3.org/TR/performance-timeline-2/#dom-performanceobserver
    - Performance Observer -

    View full-size slide

  73. - Performance Timeline -
    The developer is encouraged to use
    PerformanceObserver where possible.
    Further, new performance APIs and metrics
    may only be available through the
    PerformanceObserver interface.
    www.w3.org/TR/performance-timeline-2/#introduction

    View full-size slide

  74. - Paint Timing -
    www.w3.org/TR/paint-timing/
    const perfObserver = new PerformanceObserver(list => {
    list.getEntries().forEach((entry) => {
    // Process entries
    // report back for analytics and monitoring
    // entry.name -> 'first-paint'
    // entry.name -> 'first-contentful-paint'
    } );
    });
    perfObserver.observe({entryTypes: ['paint']});

    View full-size slide

  75. - Paint Timing -
    www.w3.org/TR/paint-timing/
    const perfObserver = new PerformanceObserver(list => {
    list.getEntries().forEach((entry) => {
    // Process entries
    // report back for analytics and monitoring
    // entry.name -> 'first-paint'
    // entry.name -> 'first-contentful-paint'
    } );
    });
    perfObserver.observe({entryTypes: ['paint']});

    View full-size slide

  76. - Long Task -
    github.com/w3c/longtasks
    var perfObserver = new PerformanceObserver(function(list) {
    list.getEntries().forEach((entry) => {
    // Process entries
    // report back for analytics and monitoring
    // ...
    });
    });
    perfObserver.observe({entryTypes: ['longtask']});

    View full-size slide

  77. - Long Task -
    github.com/w3c/longtasks
    var perfObserver = new PerformanceObserver(function(list) {
    list.getEntries().forEach((entry) => {
    // Process entries
    // report back for analytics and monitoring
    // ...
    });
    });
    perfObserver.observe({entryTypes: ['longtask']});

    View full-size slide

  78. src="hero.jpg"
    onload="performance.clearMarks('img displayed');
    performance.mark('img displayed');">
    <br/>performance.clearMarks('img displayed');<br/>performance.mark('img displayed');<br/>
    - Hero Element Timing -
    speedcurve.com/blog/user-timing-and-custom-metrics/

    View full-size slide

  79. - Hero Element Timing -
    docs.google.com/document/d/1yRYfYR1DnHtgwC4HRR04ipVVhT1h5gkI6yPmKCgJkyQ/edit

    const perfObserver = new PerformanceObserver(list => {
    list.getEntries().forEach((entry) => {
    // Process entries
    // report back for analytics and monitoring
    // ...
    });
    });
    perfObserver.observe({entryTypes: ['element']});
    github.com/w3c/charter-webperf/issues/30

    View full-size slide

  80. - Hero Element Timing -
    docs.google.com/document/d/1yRYfYR1DnHtgwC4HRR04ipVVhT1h5gkI6yPmKCgJkyQ/edit

    const perfObserver = new PerformanceObserver(list => {
    list.getEntries().forEach((entry) => {
    // Process entries
    // report back for analytics and monitoring
    // ...
    });
    });
    perfObserver.observe({entryTypes: ['element']});
    github.com/w3c/charter-webperf/issues/30

    View full-size slide

  81. - Server Timing -
    https://developer.akamai.com/blog/2017/06/07/completing-performance-analysis-server-timing/
    Server-Timing: acl: 10
    Server-Timing: db: 125
    Server-Timing: serverName; edge.machinename.net
    https://w3c.github.io/server-timing/
    const perfObserver = new PerformanceObserver(function(list) {
    list.getEntries().forEach((entry) => {
    // Process entries
    // report back for analytics and monitoring
    // ...
    });
    })
    perfObserver.observe({entryTypes: ['server']})

    View full-size slide

  82. - Server Timing -
    https://developer.akamai.com/blog/2017/06/07/completing-performance-analysis-server-timing/
    Server-Timing: acl: 10
    Server-Timing: db: 125
    Server-Timing: serverName; edge.machinename.net
    https://w3c.github.io/server-timing/
    const perfObserver = new PerformanceObserver(function(list) {
    list.getEntries().forEach((entry) => {
    // Process entries
    // report back for analytics and monitoring
    // ...
    });
    })
    perfObserver.observe({entryTypes: ['server']})

    View full-size slide

  83. LEVERAGING
    THE METRICS
    THAT MOST AFFECT
    USER EXPERIENCE
    https://www.youtube.com/watch?v=6Ljq-Jn-EgU

    View full-size slide

  84. - Performance Observer -
    developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver
    * under consideration
    *

    View full-size slide

  85. - Performance Observer -
    we'll see...
    Polyfillable?

    View full-size slide

  86. I had to cheat a bit...

    View full-size slide

  87. Resize Observer

    View full-size slide

  88. - Resize Observer -
    (function() {
    const resizeObserver = new ResizeObserver(entries => {
    entries.forEach((entry) => {
    drawConfetti(entry);
    });
    });
    [...document.querySelectorAll('.hall-schedule__title')]
    .forEach(desc => resizeObserver.observe(desc));
    })();

    View full-size slide

  89. (function() {
    const resizeObserver = new ResizeObserver(entries => {
    entries.forEach((entry) => {
    drawConfetti(entry);
    });
    });
    [...document.querySelectorAll('.hall-schedule__title')]
    .forEach(desc => resizeObserver.observe(desc));
    })();
    - Resize Observer -

    View full-size slide

  90. (function() {
    const resizeObserver = new ResizeObserver(entries => {
    entries.forEach((entry) => {
    drawConfetti(entry);
    });
    });
    [...document.querySelectorAll('.hall-schedule__title')]
    .forEach(desc => resizeObserver.observe(desc));
    })();
    - Resize Observer -

    View full-size slide

  91. - Resize Observer -
    Observation also fires when
    watched Element is inserted/removed from DOM
    watched Element display gets set to none
    Observation does not fire for
    triggered CSS transforms

    wicg.github.io/ResizeObserver/#intro

    View full-size slide

  92. - Resize Observer -
    developers.google.com/web/updates/2016/10/resizeobserver
    const ro = new ResizeObserver((entries) => {
    document.scrollingElement.scrollTop =
    document.scrollingElement.scrollHeight;
    });
    // Observe the scrollingElement
    // for when the window gets resized
    ro.observe(document.scrollingElement);
    // Observe the timeline
    // to process new messages
    ro.observe(timeline);

    View full-size slide

  93. - Resize Observer -
    developers.google.com/web/updates/2016/10/resizeobserver
    const ro = new ResizeObserver((entries) => {
    document.scrollingElement.scrollTop =
    document.scrollingElement.scrollHeight;
    });
    // Observe the scrollingElement
    // for when the window gets resized
    ro.observe(document.scrollingElement);
    // Observe the timeline
    // to process new messages
    ro.observe(timeline);

    View full-size slide

  94. - Resize Observer -

    View full-size slide

  95. - Resize Observer -

    View full-size slide

  96. - Resize Observer -
    developers.google.com/web/updates/2016/10/resizeobserver
    * under consideration
    ** behind a flag
    **
    * **

    View full-size slide

  97. - Resize Observer -
    Polyfillable?
    github.com/que-etc/resize-observer-polyfill
    so, so...

    View full-size slide

  98. But let's face
    the resize problem
    Photo by madstreetz

    View full-size slide

  99. - Event streams -
    element

    View full-size slide

  100. - Event streams -
    element
    initial call

    View full-size slide

  101. - Event streams -
    element
    initial call

    View full-size slide

  102. - Event streams -
    element
    initial call resize resize resize
    opening transition

    View full-size slide

  103. element
    initial call resize resize resize
    - Event streams -
    opening transition

    View full-size slide

  104. element
    initial call resize resize resize resize resize resize
    - Event streams -
    opening transition
    closing transition

    View full-size slide

  105. - Event streams -
    element
    initial call resize resize resize resize resize resize
    opening transition
    closing transition

    View full-size slide

  106. - Event streams -
    onResize(entry => {
    drawConfetti(entry);
    });

    View full-size slide

  107. - Event streams -
    onResize(entry => {
    drawConfetti(entry);
    });

    View full-size slide

  108. - Event streams -
    });
    let isFirst = true;
    onResize(entry => {
    if (isFirst) {
    isFirst = false;
    return;
    }
    drawConfetti(entry);

    View full-size slide

  109. - Event streams -
    });
    let isFirst = true;
    onResize(entry => {
    if (isFirst) {
    isFirst = false;
    return;
    }
    drawConfetti(entry);

    View full-size slide

  110. - Event streams -
    });
    let isFirst = true;
    let last;
    onResize(entry => {
    if (isFirst) {
    isFirst = false;
    return;
    }
    if (
    last && last.contentRect.height < entry.contentRect.height
    ) {
    drawConfetti(entry);
    last = entry;
    }

    View full-size slide

  111. - Observables -
    A collection
    that arrives over time.

    View full-size slide

  112. - Observables -
    function getObservableWithThreeValues () {
    return new Observable((observer) => {
    observer.next(1);
    observer.next(2);
    observer.next(3);
    observer.complete();
    });
    }
    const observer = {
    next(value) { console.log('next:', value) },
    error(err) { console.error(err) },
    complete() { console.log('We are done') },
    }
    const subscription = getObservableWithThreeValues().subscribe(observer)
    // next: 2
    // next: 4
    // next: 6
    // We are done

    View full-size slide

  113. - Observables -
    function getObservableWithThreeValues () {
    return new Observable((observer) => {
    observer.next(1);
    observer.next(2);
    observer.next(3);
    observer.complete();
    });
    }
    const observer = {
    next(value) { console.log('next:', value) },
    error(err) { console.error(err) },
    complete() { console.log('We are done') },
    };
    const subscription = getObservableWithThreeValues().subscribe(observer)
    // next: 2
    // next: 4
    // next: 6
    // We are done

    View full-size slide

  114. - Observables -
    function getObservableWithThreeValues () {
    return new Observable((observer) => {
    observer.next(1);
    observer.next(2);
    observer.next(3);
    observer.complete();
    });
    }
    const observer = {
    next(value) { console.log('next:', value) },
    error(err) { console.error(err) },
    complete() { console.log('We are done') },
    };
    const subscription = getObservableWithThreeValues().subscribe(observer);
    // next: 1
    // next: 2
    // next: 3
    // We are done

    View full-size slide

  115. - Observables -
    It's a collection!

    View full-size slide

  116. - Observables -
    It's a collection!
    // like Array.prototype.map
    observable.map
    // like Array.prototype.filter
    observable.filter
    // like Array.prototype.reduce
    observable.reduce

    View full-size slide

  117. - Observables -
    function getResizeStream(elem) {
    return Rx.Observable.create((observer) => {
    const resizeObserver = new ResizeObserver((entries) => {
    entries.forEach(entry => {
    drawConfetti(entry);
    });
    });
    resizeObserver.observe(elem);
    })
    }

    View full-size slide

  118. - Observables -
    function getResizeStream(elem) {
    return Rx.Observable.create((observer) => {
    const resizeObserver = new ResizeObserver((entries) => {
    entries.forEach(entry => {
    observer.next(entry);
    });
    });
    resizeObserver.observe(elem);
    });
    }

    View full-size slide

  119. - Observables -
    function getResizeStream(elem) {
    return Rx.Observable.create((observer) => {
    const resizeObserver = new ResizeObserver(entries => {
    entries.forEach(entry => {
    observer.next(entry);
    })
    });
    resizeObserver.observe(elem);
    });
    }
    Collection super
    powers

    View full-size slide

  120. - Observables -
    const subscription = getResizeStream(elem);
    .skip(1)
    .pairwise()
    .filter(([prev, current]) => {
    return prev.contentRect.height < current.contentRect.height;
    })
    .map(([prev, current]) => current)
    .subscribe({
    next: (entry) => drawConfetti(entry),
    error: console.error,
    complete: () => { console.log('Complete!') }
    });
    element
    initial call opening transition closing transition

    View full-size slide

  121. - Observables -
    const subscription = getResizeStream(elem)
    .skip(1);
    .pairwise()
    .filter(([prev, current]) => {
    return prev.contentRect.height < current.contentRect.height;
    })
    .map(([prev, current]) => current)
    .subscribe({
    next: (entry) => drawConfetti(entry),
    error: console.error,
    complete: () => { console.log('Complete!') }
    });
    element
    initial call opening transition closing transition

    View full-size slide

  122. - Observables -
    const subscription = getResizeStream(elem)
    .skip(1);
    .pairwise()
    .filter(([prev, current]) => {
    return prev.contentRect.height < current.contentRect.height;
    })
    .map(([prev, current]) => current)
    .subscribe({
    next: (entry) => drawConfetti(entry),
    error: console.error,
    complete: () => { console.log('Complete!') }
    });
    element
    initial call opening transition closing transition

    View full-size slide

  123. - Observables -
    const subscription = getResizeStream(elem)
    .skip(1)
    .pairwise();
    .filter(([prev, current]) => {
    return prev.contentRect.height < current.contentRect.height;
    })
    .map(([prev, current]) => current)
    .subscribe({
    next: (entry) => drawConfetti(entry),
    error: console.error,
    complete: () => { console.log('Complete!') }
    });
    element
    initial call opening transition closing transition

    View full-size slide

  124. - Observables -
    const subscription = getResizeStream(elem)
    .skip(1)
    .pairwise();
    .filter(([prev, current]) => {
    return prev.contentRect.height < current.contentRect.height;
    })
    .map(([prev, current]) => current)
    .subscribe({
    next: (entry) => drawConfetti(entry),
    error: console.error,
    complete: () => { console.log('Complete!') }
    });
    element
    initial call opening transition closing transition

    View full-size slide

  125. - Observables -
    const subscription = getResizeStream(elem)
    .skip(1)
    .pairwise()
    .filter(([prev, current]) => {
    return prev.contentRect.height < current.contentRect.height;
    });
    .map(([prev, current]) => current)
    .subscribe({
    next: (entry) => drawConfetti(entry),
    error: console.error,
    complete: () => { console.log('Complete!') }
    });
    element
    initial call opening transition closing transition

    View full-size slide

  126. - Observables -
    const subscription = getResizeStream(elem)
    .skip(1)
    .pairwise()
    .filter(([prev, current]) => {
    return prev.contentRect.height < current.contentRect.height;
    });
    .map(([prev, current]) => current)
    .subscribe({
    next: (entry) => drawConfetti(entry),
    error: console.error,
    complete: () => { console.log('Complete!') }
    });
    element
    initial call opening transition closing transition

    View full-size slide

  127. - Observables -
    const subscription = getResizeStream(elem)
    .skip(1)
    .pairwise()
    .filter(([prev, current]) => {
    return prev.contentRect.height < current.contentRect.height;
    })
    .map(([prev, current]) => current);
    .subscribe({
    next: (entry) => drawConfetti(entry),
    error: console.error,
    complete: () => { console.log('Complete!') }
    });
    element
    initial call opening transition closing transition

    View full-size slide

  128. - Observables -
    const subscription = getResizeStream(elem)
    .skip(1)
    .pairwise()
    .filter(([prev, current]) => {
    return prev.contentRect.height < current.contentRect.height;
    })
    .map(([prev, current]) => current);
    .subscribe({
    next: (entry) => drawConfetti(entry),
    error: console.error,
    complete: () => { console.log('Complete!') }
    });
    element
    initial call opening transition closing transition

    View full-size slide

  129. - Observables -
    const subscription = getResizeStream(elem)
    .skip(1)
    .pairwise()
    .filter(([prev, current]) => {
    return prev.contentRect.height < current.contentRect.height;
    })
    .map(([prev, current]) => current)
    .subscribe({
    next: (entry) => drawConfetti(entry),
    error: console.error,
    complete: () => { console.log('Complete!'); }
    });
    element
    initial call opening transition closing transition

    View full-size slide

  130. Intersection
    Observer
    Mutation
    Observer
    Performance
    Observer
    Resize
    Observer
    Observables

    View full-size slide

  131. So much has changed

    View full-size slide

  132. So much will change

    View full-size slide

  133. Let's celebrate!

    View full-size slide

  134. Let's celebrate!

    View full-size slide

  135. Let's celebrate!
    Thanks.
    @stefanjudis
    Slides
    ctfl.io/watch-your-back-browser

    View full-size slide