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 Slide

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

    View Slide

  3. View Slide

  4. I'm excited about
    conferences and meetups

    View Slide

  5. The web platform evolves
    really fast these days

    View Slide

  6. Staying up to date is
    part of our job

    View Slide

  7. JavaScript Ecosystem

    View Slide

  8. Browser APIs adapt
    common use cases

    View Slide

  9. Browser APIs adapt
    common use cases
    pull

    View Slide

  10. Browser APIs adapt
    common use cases
    pull push

    View Slide

  11. Let's celebrate!

    View Slide

  12. View Slide

  13. View Slide

  14. codepen.io/thebabydino/pen/RRRRZE

    View Slide

  15. codepen.io/thebabydino/pen/RRRRZE

    View Slide

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

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

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

    View Slide

  19. 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 Slide

  20. 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 Slide

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

    View Slide

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

    View Slide

  23. View Slide

  24. View Slide

  25. View Slide

  26. View Slide

  27. Intersection Observer

    View Slide

  28. - 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 Slide

  29. - 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 Slide

  30. - 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 Slide

  31. (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 Slide

  32. View Slide

  33. View Slide

  34. Measure it!

    View Slide

  35. - 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 Slide

  36. - 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 Slide

  37. - 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 Slide

  38. // 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 Slide

  39. - Intersection Observer -
    threshold = 0
    viewport

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  45. - 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 Slide

  46. - 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 Slide

  47. twitter.com/jaffathecake/status/857537625154097152

    View Slide

  48. twitter.com/jaffathecake/status/857537625154097152

    View Slide

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

    View Slide

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

    View Slide

  51. - 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 Slide

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

    View Slide

  53. Let's celebrate the
    whole schedule!

    View Slide

  54. View Slide

  55. View Slide

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

    View Slide

  57. Mutation Observer

    View Slide

  58. - 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 Slide

  59. (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 Slide

  60. (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 Slide

  61. (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 Slide

  62. 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 Slide

  63. 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 Slide

  64. 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 Slide

  65. 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 Slide

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

    View Slide

  67. But let's always
    watch out
    for performance

    View Slide

  68. WAYS TO MEASURE PERFORMANCE
    Synthetic
    Monitoring
    Real User
    Monitoring

    View Slide

  69. View Slide

  70. View Slide

  71. View Slide

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

    View Slide

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

    View Slide

  74. - 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 Slide

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

    View Slide

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

    View Slide

  77. - 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 Slide

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

    View Slide

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

    View Slide

  80. - 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 Slide

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

    View Slide

  82. - 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 Slide

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

    View Slide

  84. Performance Observer

    View Slide

  85. - 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 Slide

  86. (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 Slide

  87. (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 Slide

  88. - 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 Slide

  89. - 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 Slide

  90. - 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 Slide

  91. - 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 Slide

  92. - 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 Slide

  93. View Slide

  94. View Slide

  95. View Slide

  96. View Slide

  97. 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 Slide

  98. - 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 Slide

  99. - 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 Slide

  100. - 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 Slide

  101. - 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

  105. View Slide

  106. I had to cheat a bit...

    View Slide

  107. View Slide

  108. View Slide

  109. Resize Observer

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  113. - 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 Slide

  114. View Slide

  115. View Slide

  116. - 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 Slide

  117. - 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 Slide

  118. - Resize Observer -

    View Slide

  119. - Resize Observer -

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  123. - Event streams -
    element

    View Slide

  124. - Event streams -
    element
    initial call

    View Slide

  125. - Event streams -
    element
    initial call

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  134. - 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 Slide

  135. Observables

    View Slide

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

    View Slide

  137. - 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 Slide

  138. - 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 Slide

  139. - 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 Slide

  140. - Observables -
    It's a collection!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  144. - 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 Slide

  145. - 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 Slide

  146. - 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 Slide

  147. - 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 Slide

  148. - 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 Slide

  149. - 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 Slide

  150. - 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 Slide

  151. - 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 Slide

  152. - 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 Slide

  153. - 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 Slide

  154. - 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 Slide

  155. View Slide

  156. View Slide

  157. Intersection
    Observer
    Mutation
    Observer
    Performance
    Observer
    Resize
    Observer
    Observables

    View Slide

  158. So much has changed

    View Slide

  159. So much will change

    View Slide

  160. Let's celebrate!

    View Slide

  161. Let's celebrate!

    View Slide

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

    View Slide