Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Media in the Age of PWAs [ImageCon 2019]

140 views

Published on

Our industry is abuzz with talk about Progressive Web Apps (PWAs) and with good reason: they are a great way to improve the experiences our users have on our sites, especially when it comes to performance. Using Service Workers—a key component of PWAs—we can manage network requests and the cache to an incredibly granular degree. We can also totally abuse the privilege Service Workers grant us when it comes to writing files to disk.

In this session, Aaron Gustafson will discuss some of the potential pitfalls in implementing Service Workers, especially when it comes to managing heavy files like images and video. He’ll provide guidance on current best practices in cache management. And he’ll offer a few simple recipes you can put to use right away to deliver amazing experiences for your users that respect their data usage and disk space.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Media in the Age of PWAs [ImageCon 2019]

  1. 1. Media in the Age of PWAs Aaron Gustafson @AaronGustafson slideshare.net/AaronGustafson
  2. 2. 
 PWA? What exactly is a

  3. 3. Progressive Web App? What exactly is a

  4. 4. What exactly is a Progressive Web App?
  5. 5. What exactly is a Progressive Web App?
  6. 6. What exactly is a Progressive Web App?
  7. 7. “Progressive Web App”
 is a marketing term Progressive Web App

  8. 8. Progressive Web App

  9. 9. Progressive Web App
 Game Gallery Book Newspaper Art Project
  10. 10. Progressive Web Site

  11. 11. Who’s behind PWAs?

  12. 12. @AaronGustafson A Minimum Viable PWA HTTPS
  13. 13. @AaronGustafson A Minimum Viable PWA HTTPS Web App
 Manifest
  14. 14. @AaronGustafson Web App Manifest { "lang": "en", "short_name": "Wash Post", "name": "The Washington Post", "icons": [ { "src": "img/launcher-icon-2x.png", "sizes": "96x96", "type": "image/png" } ], "start_url": "/pwa/", "display": "standalone", "orientation": "portrait", "background_color": "black" }
  15. 15. @AaronGustafson A Minimum Viable PWA HTTPS Web App
 Manifest Service Worker
  16. 16. Should you
 believe the hype?
  17. 17. Maybe?
  18. 18. Carnival:
 24% opt-in rate and 42% open rate for push notifications Katarzyna Ostrowska aka.ms/carnival-pwa
  19. 19. Starbucks:
 2x increase in daily active users aka.ms/google-io-2018
  20. 20. Tinder:
 Core experience
 with 90% less code aka.ms/tinder-pwa-2017
  21. 21. Trivago:
 97% increase in
 click-outs to 
 hotel offers aka.ms/trivago-pwa-2017
  22. 22. West Elm:
 15% increase in
 time on site
 9% increase in revenue per visit aka.ms/west-elm-pwa-2017
  23. 23. @AaronGustafson A Minimum Viable PWA HTTPS Web App
 Manifest Service Worker
  24. 24. @AaronGustafson Let’s talk about Service Worker
  25. 25. @AaronGustafson Registering a Service Worker if ( "serviceWorker" in navigator ) {
 
 navigator.serviceWorker.register( "/serviceworker.min.js" );
 
 }
  26. 26. @AaronGustafson Registering a Service Worker if ( "serviceWorker" in navigator ) {
 
 navigator.serviceWorker.register( "/serviceworker.min.js" );
 
 }
  27. 27. @AaronGustafson Registering a Service Worker if ( "serviceWorker" in navigator ) {
 
 navigator.serviceWorker.register( "/serviceworker.min.js" );
 
 } Path is important!
  28. 28. @AaronGustafson The Service Worker Lifecycle Browser Install Activation Ready aka.ms/pwa-lifecycle
  29. 29. @AaronGustafson How connections are made Browser Internet
  30. 30. @AaronGustafson Along comes Service Worker Browser Internet Cache
  31. 31. @AaronGustafson Along comes Service Worker Browser Internet Cache !
  32. 32. @AaronGustafson Along comes Service Worker Browser Internet Cache
  33. 33. @AaronGustafson Know your (storage) limits Temporary Persistent Browser purges User purges
  34. 34. @AaronGustafson Know your (storage) limits Volume Size Domain Limit Overall Limit ≤ 8 GB 20%
 of
 overall 50 MB 8–32 GB 500 MB 32–128 GB 4% of volume > 128 GB 4% or 20 GB
  35. 35. Except on iOS.
 Safari gives you 50 MB.
  36. 36. Raising limits?
 Unlimited storage?
  37. 37. Storage is a privilege,
 don’t abuse it.
  38. 38. How?
  39. 39. @AaronGustafson #1: No animated GIFs
  40. 40. @AaronGustafson #2: Use responsive images 41 <img src="medium.jpg"
 srcset="small.jpg 256w,
 medium.jpg 512w,
 large.jpg 1024w"
 sizes="(max-width: 30em) 30em, 100vw"
 alt="It’s responsive!"> aka.ms/cloudinary-images
  41. 41. @AaronGustafson #3: Lazy load images 42 <img src="medium.jpg"
 srcset="small.jpg 256w,
 medium.jpg 512w,
 large.jpg 1024w"
 sizes="(max-width: 30em) 30em, 100vw"
 loading="lazy"
 alt="It’s responsive and lazy loads!"> aka.ms/img-lazy-loading
  42. 42. @AaronGustafson #4: Provide alternate formats 43 <picture>
 <source type="image/webp" srcset="my.webp">
 <img src="my.jpg" alt="Alt text goes here">
 </picture>
  43. 43. @AaronGustafson #4: Provide alternate formats via Cloudinary URLs: 44 https://res.cloudinary.com/demo/image/upload/w_300,f_auto/my.jpg aka.ms/cloudinary-webp
  44. 44. @AaronGustafson #5: Have fallback images 45 self.addEventListener( "install", function( event ){
 event.waitUntil(
 caches.open( "static" ).then( cache => {
 return cache.addAll( ["/i/fallbacks/offline.svg"] );
 })
 );
 });
 
 function respondWithOfflineImage() {
 return caches.match( "/i/fallbacks/offline.svg" );
 } aka.ms/ag-sw
  45. 45. @AaronGustafson #5: Have fallback images 46 self.addEventListener( "install", function( event ){
 event.waitUntil(
 caches.open( "static" ).then( cache => {
 return cache.addAll( ["/i/fallbacks/offline.svg"] );
 })
 );
 });
 
 function respondWithOfflineImage() {
 return caches.match( "/i/fallbacks/offline.svg" );
 } aka.ms/ag-sw
  46. 46. @AaronGustafson #5: Have fallback images 47 self.addEventListener( "install", function( event ){
 event.waitUntil(
 caches.open( "static" ).then( cache => {
 return cache.addAll( ["/i/fallbacks/offline.svg"] );
 })
 );
 });
 
 function respondWithOfflineImage() {
 return caches.match( "/i/fallbacks/offline.svg" );
 } aka.ms/ag-sw
  47. 47. @AaronGustafson #5: Have fallback images 48 self.addEventListener( "install", function( event ){
 event.waitUntil(
 caches.open( "static" ).then( cache => {
 return cache.addAll( ["/i/fallbacks/offline.svg"] );
 })
 );
 });
 
 function respondWithOfflineImage() {
 return caches.match( "/i/fallbacks/offline.svg" );
 } aka.ms/ag-sw
  48. 48. @AaronGustafson #5: Have fallback images 49 self.addEventListener( "install", function( event ){
 event.waitUntil(
 caches.open( "static" ).then( cache => {
 return cache.addAll( ["/i/fallbacks/offline.svg"] );
 })
 );
 });
 
 function respondWithOfflineImage() {
 return caches.match( "/i/fallbacks/offline.svg" );
 } aka.ms/ag-sw
  49. 49. @AaronGustafson #5: Have fallback images 50 self.addEventListener( "install", function( event ){
 event.waitUntil(
 caches.open( "static" ).then( cache => {
 return cache.addAll( ["/i/fallbacks/offline.svg"] );
 })
 );
 });
 
 function respondWithOfflineImage() {
 return caches.match( "/i/fallbacks/offline.svg" );
 } aka.ms/ag-sw
  50. 50. @AaronGustafson #5: Have fallback images 51 self.addEventListener( "fetch", event => {
 const request = event.request,
 url = request.url;
 if ( request.headers.get("Accept").includes("image") ) {
 event.respondWith(
 return fetch( request, fetch_config.images )
 .then( response => {
 return response;
 })
 .catch(
 respondWithOfflineImage
 );
 );
 }
 });
 
 aka.ms/ag-sw
  51. 51. @AaronGustafson #5: Have fallback images 52 self.addEventListener( "fetch", event => {
 const request = event.request,
 url = request.url;
 if ( request.headers.get("Accept").includes("image") ) {
 event.respondWith(
 return fetch( request, fetch_config.images )
 .then( response => {
 return response;
 })
 .catch(
 respondWithOfflineImage
 );
 );
 }
 });
 
 aka.ms/ag-sw
  52. 52. @AaronGustafson #5: Have fallback images 53 self.addEventListener( "fetch", event => {
 const request = event.request,
 url = request.url;
 if ( request.headers.get("Accept").includes("image") ) {
 event.respondWith(
 return fetch( request, fetch_config.images )
 .then( response => {
 return response;
 })
 .catch(
 respondWithOfflineImage
 );
 );
 }
 });
 
 aka.ms/ag-sw
  53. 53. @AaronGustafson #5: Have fallback images 54 self.addEventListener( "fetch", event => {
 const request = event.request,
 url = request.url;
 if ( request.headers.get("Accept").includes("image") ) {
 event.respondWith(
 return fetch( request, fetch_config.images )
 .then( response => {
 return response;
 })
 .catch(
 respondWithOfflineImage
 );
 );
 }
 });
 
 aka.ms/ag-sw
  54. 54. @AaronGustafson #5: Have fallback images 55 self.addEventListener( "fetch", event => {
 const request = event.request,
 url = request.url;
 if ( request.headers.get("Accept").includes("image") ) {
 event.respondWith(
 return fetch( request, fetch_config.images )
 .then( response => {
 return response;
 })
 .catch(
 respondWithOfflineImage
 );
 );
 }
 });
 
 aka.ms/ag-sw
  55. 55. @AaronGustafson #5: Have fallback images 56 self.addEventListener( "fetch", event => {
 const request = event.request,
 url = request.url;
 if ( request.headers.get("Accept").includes("image") ) {
 event.respondWith(
 return fetch( request, fetch_config.images )
 .then( response => {
 return response;
 })
 .catch(
 respondWithOfflineImage
 );
 );
 }
 });
 
 aka.ms/ag-sw
  56. 56. @AaronGustafson Result!
  57. 57. @AaronGustafson Result!
  58. 58. @AaronGustafson #6: Respect Save Data 58 let save_data = false;
 if ( 'connection' in navigator ) {
 save_data = navigator.connection.saveData;
 } aka.ms/ag-sw
  59. 59. @AaronGustafson #6: Respect Save Data 59 self.addEventListener( "fetch", event => {
 const request = event.request,
 url = request.url;
 if ( request.headers.get("Accept").includes("image") ) {
 event.respondWith(
 if ( save_data ) {
 return respondWithFallbackImage( url );
 }
 // …
 );
 }
 
 aka.ms/ag-sw
  60. 60. @AaronGustafson #6: Respect Save Data 60 const fallback_avatar = "/i/fallbacks/avatar.svg",
 fallback_image = "/i/fallbacks/image.svg",
 avatars = /webmention.io/;
 
 // …
 
 function respondWithFallbackImage( url ) {
 const image = avatars.test( url ) ? fallback_avatar
 : fallback_image;
 return caches.match( image );
 } aka.ms/ag-sw
  61. 61. @AaronGustafson #6: Respect Save Data 61 const fallback_avatar = "/i/fallbacks/avatar.svg",
 fallback_image = "/i/fallbacks/image.svg",
 avatars = /webmention.io/;
 
 // …
 
 function respondWithFallbackImage( url ) {
 const image = avatars.test( url ) ? fallback_avatar
 : fallback_image;
 return caches.match( image );
 } aka.ms/ag-sw
  62. 62. @AaronGustafson #6: Respect Save Data 62 const fallback_avatar = "/i/fallbacks/avatar.svg",
 fallback_image = "/i/fallbacks/image.svg",
 avatars = /webmention.io/;
 
 // …
 
 function respondWithFallbackImage( url ) {
 const image = avatars.test( url ) ? fallback_avatar
 : fallback_image;
 return caches.match( image );
 } aka.ms/ag-sw
  63. 63. @AaronGustafson Result!
  64. 64. @AaronGustafson #7: Prioritize certain images 64 const high_priority = [
 /aaron-gustafson.com/,
 /adaptivewebdesign.info/
 ];
 
 function isHighPriority( url ) {
 let i = high_priority.length;
 while ( i-- ) {
 if ( high_priority[i].test( url ) ) {
 return true;
 }
 }
 return false;
 } 
 aka.ms/ag-sw
  65. 65. @AaronGustafson #7: Prioritize certain images 65 const high_priority = [
 /aaron-gustafson.com/,
 /adaptivewebdesign.info/
 ];
 
 function isHighPriority( url ) {
 let i = high_priority.length;
 while ( i-- ) {
 if ( high_priority[i].test( url ) ) {
 return true;
 }
 }
 return false;
 } 
 aka.ms/ag-sw
  66. 66. @AaronGustafson #7: Prioritize certain images 66 const high_priority = [
 /aaron-gustafson.com/,
 /adaptivewebdesign.info/
 ];
 
 function isHighPriority( url ) {
 let i = high_priority.length;
 while ( i-- ) {
 if ( high_priority[i].test( url ) ) {
 return true;
 }
 }
 return false;
 } 
 aka.ms/ag-sw
  67. 67. @AaronGustafson #8: Clean up after yourself 67 const version = "v2:",
 sw_caches = {
 static: {
 name: `${version}static`
 },
 images: {
 name: `${version}images`,
 limit: 75
 },
 pages: {
 name: `${version}pages`,
 limit: 5
 },
 posts: {
 name: `${version}posts`,
 limit: 10
 },
 other: {
 name: `${version}other`,
 limit: 50
 }
 }; aka.ms/ag-sw
  68. 68. @AaronGustafson #8: Clean up after yourself 68 function trimCache( cache_name, limit ) {
 caches.open( cache_name ).then( cache => {
 cache.keys().then( items => {
 if ( items.length > limit ) {
 cache.delete( items[0] ).then(
 trimCache( cache_name, limit )
 );
 }
 });
 });
 } aka.ms/ag-sw
  69. 69. @AaronGustafson #8: Clean up after yourself 69 function trimCache( cache_name, limit ) {
 caches.open( cache_name ).then( cache => {
 cache.keys().then( items => {
 if ( items.length > limit ) {
 cache.delete( items[0] ).then(
 trimCache( cache_name, limit )
 );
 }
 });
 });
 } aka.ms/ag-sw
  70. 70. @AaronGustafson #8: Clean up after yourself 70 function trimCache( cache_name, limit ) {
 caches.open( cache_name ).then( cache => {
 cache.keys().then( items => {
 if ( items.length > limit ) {
 cache.delete( items[0] ).then(
 trimCache( cache_name, limit )
 );
 }
 });
 });
 } aka.ms/ag-sw
  71. 71. @AaronGustafson #8: Clean up after yourself 71 if ( "serviceWorker" in navigator ) {
 navigator.serviceWorker.register( "/serviceworker.min.js" );
 
 if ( navigator.serviceWorker.controller ) {
 window.addEventListener( "load", function(){
 navigator.serviceWorker.controller.postMessage( "clean up" );
 });
 }
 } aka.ms/ag-sw
  72. 72. @AaronGustafson #8: Clean up after yourself 72 addEventListener("message", messageEvent => {
 if (messageEvent.data == "clean up") {
 for ( let key in sw_caches ) {
 if ( sw_caches[key].limit != undefined ) {
 trimCache( sw_caches[key].name, sw_caches[key].limit );
 }
 }
 }
 }); aka.ms/ag-sw
  73. 73. @AaronGustafson #8: Clean up after yourself 73 addEventListener("message", messageEvent => {
 if (messageEvent.data == "clean up") {
 for ( let key in sw_caches ) {
 if ( sw_caches[key].limit != undefined ) {
 trimCache( sw_caches[key].name, sw_caches[key].limit );
 }
 }
 }
 }); aka.ms/ag-sw
  74. 74. @AaronGustafson Make good choices 1. No animated GIFs (especially as backgrounds) 2. Use responsive images 3. Lazy load images 4. Provide alternate image formats 5. Provide fallback images via Service Worker 6. Pay attention to the Save Data header 7. Prioritize certain images 8. Clean up after yourself 74
  75. 75. © Marvel
  76. 76. Thank you! @AaronGustafson aaron-gustafson.com slideshare.net/AaronGustafson

×