SlideShare a Scribd company logo
1 of 77
Download to read offline
Media in the
Age of PWAs
Aaron Gustafson
@AaronGustafson
slideshare.net/AaronGustafson


PWA?
What exactly is a

Progressive Web App?
What exactly is a

What exactly is a
Progressive Web App?
What exactly is a
Progressive Web App?
What exactly is a
Progressive Web App?
“Progressive Web App”

is a marketing term
Progressive Web App

Progressive Web App

Progressive Web App

Game
Gallery
Book
Newspaper
Art Project
Progressive Web Site

Who’s behind PWAs?

@AaronGustafson
A Minimum Viable PWA
HTTPS
@AaronGustafson
A Minimum Viable PWA
HTTPS Web App

Manifest
@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"	
}
@AaronGustafson
A Minimum Viable PWA
HTTPS Web App

Manifest
Service
Worker
Should you

believe the hype?
Maybe?
Carnival:

24% opt-in rate and
42% open rate for
push notifications
Katarzyna Ostrowska
aka.ms/carnival-pwa
Starbucks:

2x increase in daily
active users
aka.ms/google-io-2018
Tinder:

Core experience

with 90% less code
aka.ms/tinder-pwa-2017
Trivago:

97% increase in

click-outs to 

hotel offers
aka.ms/trivago-pwa-2017
West Elm:

15% increase in

time on site

9% increase in
revenue per visit
aka.ms/west-elm-pwa-2017
@AaronGustafson
A Minimum Viable PWA
HTTPS Web App

Manifest
Service
Worker
@AaronGustafson
Let’s talk about Service Worker
@AaronGustafson
Registering a Service Worker
if	(	"serviceWorker"	in	navigator	)	{



		navigator.serviceWorker.register(	"/serviceworker.min.js"	);



}
@AaronGustafson
Registering a Service Worker
if	(	"serviceWorker"	in	navigator	)	{



		navigator.serviceWorker.register(	"/serviceworker.min.js"	);



}
@AaronGustafson
Registering a Service Worker
if	(	"serviceWorker"	in	navigator	)	{



		navigator.serviceWorker.register(	"/serviceworker.min.js"	);



}
Path is important!
@AaronGustafson
The Service Worker Lifecycle
Browser
Install Activation Ready
aka.ms/pwa-lifecycle
@AaronGustafson
How connections are made
Browser
Internet
@AaronGustafson
Along comes Service Worker
Browser
Internet
Cache
@AaronGustafson
Along comes Service Worker
Browser
Internet
Cache
!
@AaronGustafson
Along comes Service Worker
Browser
Internet
Cache
@AaronGustafson
Know your (storage) limits
Temporary Persistent
Browser purges User purges
@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
Except on iOS.

Safari gives you 50 MB.
Raising limits?

Unlimited storage?
Storage is a privilege,

don’t abuse it.
How?
@AaronGustafson
#1: No animated GIFs
@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
@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
@AaronGustafson
#4: Provide alternate formats
43
<picture>

		<source	type="image/webp"	srcset="my.webp">

		<img	src="my.jpg"	alt="Alt	text	goes	here">

</picture>
@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
@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
@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
@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
@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
@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
@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
@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
@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
@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
@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
@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
@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
@AaronGustafson
Result!
@AaronGustafson
Result!
@AaronGustafson
#6: Respect Save Data
58
let	save_data	=	false;

if	(	'connection'	in	navigator	)	{

		save_data	=	navigator.connection.saveData;

}
aka.ms/ag-sw
@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
@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
@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
@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
@AaronGustafson
Result!
@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
@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
@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
@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
@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
@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
@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
@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
@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
@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
@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
© Marvel
Thank you!
@AaronGustafson
aaron-gustafson.com
slideshare.net/AaronGustafson

More Related Content

More from Aaron Gustafson

Delivering Critical Information and Services [JavaScript & Friends 2021]
Delivering Critical Information and Services [JavaScript & Friends 2021]Delivering Critical Information and Services [JavaScript & Friends 2021]
Delivering Critical Information and Services [JavaScript & Friends 2021]Aaron Gustafson
 
Adapting to Reality [Guest Lecture, March 2021]
Adapting to Reality [Guest Lecture, March 2021]Adapting to Reality [Guest Lecture, March 2021]
Adapting to Reality [Guest Lecture, March 2021]Aaron Gustafson
 
Designing the Conversation [Beyond Tellerrand 2019]
Designing the Conversation [Beyond Tellerrand 2019]Designing the Conversation [Beyond Tellerrand 2019]
Designing the Conversation [Beyond Tellerrand 2019]Aaron Gustafson
 
Progressive Web Apps: Where Do I Begin?
Progressive Web Apps: Where Do I Begin?Progressive Web Apps: Where Do I Begin?
Progressive Web Apps: Where Do I Begin?Aaron Gustafson
 
Adapting to Reality [Starbucks Lunch & Learn]
Adapting to Reality [Starbucks Lunch & Learn]Adapting to Reality [Starbucks Lunch & Learn]
Adapting to Reality [Starbucks Lunch & Learn]Aaron Gustafson
 
Conversational Semantics for the Web [CascadiaJS 2018]
Conversational Semantics for the Web [CascadiaJS 2018]Conversational Semantics for the Web [CascadiaJS 2018]
Conversational Semantics for the Web [CascadiaJS 2018]Aaron Gustafson
 
Better Performance === Greater Accessibility [Inclusive Design 24 2018]
Better Performance === Greater Accessibility [Inclusive Design 24 2018]Better Performance === Greater Accessibility [Inclusive Design 24 2018]
Better Performance === Greater Accessibility [Inclusive Design 24 2018]Aaron Gustafson
 
PWA: Where Do I Begin? [Microsoft Ignite 2018]
PWA: Where Do I Begin? [Microsoft Ignite 2018]PWA: Where Do I Begin? [Microsoft Ignite 2018]
PWA: Where Do I Begin? [Microsoft Ignite 2018]Aaron Gustafson
 
Designing the Conversation [Concatenate 2018]
Designing the Conversation [Concatenate 2018]Designing the Conversation [Concatenate 2018]
Designing the Conversation [Concatenate 2018]Aaron Gustafson
 
Designing the Conversation [Accessibility DC 2018]
Designing the Conversation [Accessibility DC 2018]Designing the Conversation [Accessibility DC 2018]
Designing the Conversation [Accessibility DC 2018]Aaron Gustafson
 
Performance as User Experience [AEADC 2018]
Performance as User Experience [AEADC 2018]Performance as User Experience [AEADC 2018]
Performance as User Experience [AEADC 2018]Aaron Gustafson
 
The Web Should Just Work for Everyone
The Web Should Just Work for EveryoneThe Web Should Just Work for Everyone
The Web Should Just Work for EveryoneAaron Gustafson
 
Performance as User Experience [AEA SEA 2018]
Performance as User Experience [AEA SEA 2018]Performance as User Experience [AEA SEA 2018]
Performance as User Experience [AEA SEA 2018]Aaron Gustafson
 
Performance as User Experience [An Event Apart Denver 2017]
Performance as User Experience [An Event Apart Denver 2017]Performance as User Experience [An Event Apart Denver 2017]
Performance as User Experience [An Event Apart Denver 2017]Aaron Gustafson
 
Advanced Design Methods 1, Day 2
Advanced Design Methods 1, Day 2Advanced Design Methods 1, Day 2
Advanced Design Methods 1, Day 2Aaron Gustafson
 
Advanced Design Methods 1, Day 1
Advanced Design Methods 1, Day 1Advanced Design Methods 1, Day 1
Advanced Design Methods 1, Day 1Aaron Gustafson
 
Designing the Conversation [Paris Web 2017]
Designing the Conversation [Paris Web 2017]Designing the Conversation [Paris Web 2017]
Designing the Conversation [Paris Web 2017]Aaron Gustafson
 
Exploring Adaptive Interfaces [Generate 2017]
Exploring Adaptive Interfaces [Generate 2017]Exploring Adaptive Interfaces [Generate 2017]
Exploring Adaptive Interfaces [Generate 2017]Aaron Gustafson
 
Progressive Web Apps and the Windows Ecosystem [Build 2017]
Progressive Web Apps and the Windows Ecosystem [Build 2017]Progressive Web Apps and the Windows Ecosystem [Build 2017]
Progressive Web Apps and the Windows Ecosystem [Build 2017]Aaron Gustafson
 
Writing for Engagement [TechReady 22]
Writing for Engagement [TechReady 22]Writing for Engagement [TechReady 22]
Writing for Engagement [TechReady 22]Aaron Gustafson
 

More from Aaron Gustafson (20)

Delivering Critical Information and Services [JavaScript & Friends 2021]
Delivering Critical Information and Services [JavaScript & Friends 2021]Delivering Critical Information and Services [JavaScript & Friends 2021]
Delivering Critical Information and Services [JavaScript & Friends 2021]
 
Adapting to Reality [Guest Lecture, March 2021]
Adapting to Reality [Guest Lecture, March 2021]Adapting to Reality [Guest Lecture, March 2021]
Adapting to Reality [Guest Lecture, March 2021]
 
Designing the Conversation [Beyond Tellerrand 2019]
Designing the Conversation [Beyond Tellerrand 2019]Designing the Conversation [Beyond Tellerrand 2019]
Designing the Conversation [Beyond Tellerrand 2019]
 
Progressive Web Apps: Where Do I Begin?
Progressive Web Apps: Where Do I Begin?Progressive Web Apps: Where Do I Begin?
Progressive Web Apps: Where Do I Begin?
 
Adapting to Reality [Starbucks Lunch & Learn]
Adapting to Reality [Starbucks Lunch & Learn]Adapting to Reality [Starbucks Lunch & Learn]
Adapting to Reality [Starbucks Lunch & Learn]
 
Conversational Semantics for the Web [CascadiaJS 2018]
Conversational Semantics for the Web [CascadiaJS 2018]Conversational Semantics for the Web [CascadiaJS 2018]
Conversational Semantics for the Web [CascadiaJS 2018]
 
Better Performance === Greater Accessibility [Inclusive Design 24 2018]
Better Performance === Greater Accessibility [Inclusive Design 24 2018]Better Performance === Greater Accessibility [Inclusive Design 24 2018]
Better Performance === Greater Accessibility [Inclusive Design 24 2018]
 
PWA: Where Do I Begin? [Microsoft Ignite 2018]
PWA: Where Do I Begin? [Microsoft Ignite 2018]PWA: Where Do I Begin? [Microsoft Ignite 2018]
PWA: Where Do I Begin? [Microsoft Ignite 2018]
 
Designing the Conversation [Concatenate 2018]
Designing the Conversation [Concatenate 2018]Designing the Conversation [Concatenate 2018]
Designing the Conversation [Concatenate 2018]
 
Designing the Conversation [Accessibility DC 2018]
Designing the Conversation [Accessibility DC 2018]Designing the Conversation [Accessibility DC 2018]
Designing the Conversation [Accessibility DC 2018]
 
Performance as User Experience [AEADC 2018]
Performance as User Experience [AEADC 2018]Performance as User Experience [AEADC 2018]
Performance as User Experience [AEADC 2018]
 
The Web Should Just Work for Everyone
The Web Should Just Work for EveryoneThe Web Should Just Work for Everyone
The Web Should Just Work for Everyone
 
Performance as User Experience [AEA SEA 2018]
Performance as User Experience [AEA SEA 2018]Performance as User Experience [AEA SEA 2018]
Performance as User Experience [AEA SEA 2018]
 
Performance as User Experience [An Event Apart Denver 2017]
Performance as User Experience [An Event Apart Denver 2017]Performance as User Experience [An Event Apart Denver 2017]
Performance as User Experience [An Event Apart Denver 2017]
 
Advanced Design Methods 1, Day 2
Advanced Design Methods 1, Day 2Advanced Design Methods 1, Day 2
Advanced Design Methods 1, Day 2
 
Advanced Design Methods 1, Day 1
Advanced Design Methods 1, Day 1Advanced Design Methods 1, Day 1
Advanced Design Methods 1, Day 1
 
Designing the Conversation [Paris Web 2017]
Designing the Conversation [Paris Web 2017]Designing the Conversation [Paris Web 2017]
Designing the Conversation [Paris Web 2017]
 
Exploring Adaptive Interfaces [Generate 2017]
Exploring Adaptive Interfaces [Generate 2017]Exploring Adaptive Interfaces [Generate 2017]
Exploring Adaptive Interfaces [Generate 2017]
 
Progressive Web Apps and the Windows Ecosystem [Build 2017]
Progressive Web Apps and the Windows Ecosystem [Build 2017]Progressive Web Apps and the Windows Ecosystem [Build 2017]
Progressive Web Apps and the Windows Ecosystem [Build 2017]
 
Writing for Engagement [TechReady 22]
Writing for Engagement [TechReady 22]Writing for Engagement [TechReady 22]
Writing for Engagement [TechReady 22]
 

Recently uploaded

Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxhariprasad279825
 
Story boards and shot lists for my a level piece
Story boards and shot lists for my a level pieceStory boards and shot lists for my a level piece
Story boards and shot lists for my a level piececharlottematthew16
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfAlex Barbosa Coqueiro
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsMiki Katsuragi
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Enterprise Knowledge
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebUiPathCommunity
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
Advanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionAdvanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionDilum Bandara
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...Fwdays
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningLars Bell
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfPrecisely
 

Recently uploaded (20)

Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptx
 
Story boards and shot lists for my a level piece
Story boards and shot lists for my a level pieceStory boards and shot lists for my a level piece
Story boards and shot lists for my a level piece
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdf
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering Tips
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio Web
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
Advanced Computer Architecture – An Introduction
Advanced Computer Architecture – An IntroductionAdvanced Computer Architecture – An Introduction
Advanced Computer Architecture – An Introduction
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptxE-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine Tuning
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
 

Media in the Age of PWAs [ImageCon 2019]

  • 1. Media in the Age of PWAs Aaron Gustafson @AaronGustafson slideshare.net/AaronGustafson
  • 3. Progressive Web App? What exactly is a

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

  • 13. @AaronGustafson A Minimum Viable PWA HTTPS Web App
 Manifest
  • 15. @AaronGustafson A Minimum Viable PWA HTTPS Web App
 Manifest Service Worker
  • 18. Carnival:
 24% opt-in rate and 42% open rate for push notifications Katarzyna Ostrowska aka.ms/carnival-pwa
  • 19. Starbucks:
 2x increase in daily active users aka.ms/google-io-2018
  • 20. Tinder:
 Core experience
 with 90% less code aka.ms/tinder-pwa-2017
  • 21. Trivago:
 97% increase in
 click-outs to 
 hotel offers aka.ms/trivago-pwa-2017
  • 22. West Elm:
 15% increase in
 time on site
 9% increase in revenue per visit aka.ms/west-elm-pwa-2017
  • 23.
  • 24. @AaronGustafson A Minimum Viable PWA HTTPS Web App
 Manifest Service Worker
  • 26. @AaronGustafson Registering a Service Worker if ( "serviceWorker" in navigator ) {
 
 navigator.serviceWorker.register( "/serviceworker.min.js" );
 
 }
  • 27. @AaronGustafson Registering a Service Worker if ( "serviceWorker" in navigator ) {
 
 navigator.serviceWorker.register( "/serviceworker.min.js" );
 
 }
  • 28. @AaronGustafson Registering a Service Worker if ( "serviceWorker" in navigator ) {
 
 navigator.serviceWorker.register( "/serviceworker.min.js" );
 
 } Path is important!
  • 29. @AaronGustafson The Service Worker Lifecycle Browser Install Activation Ready aka.ms/pwa-lifecycle
  • 30. @AaronGustafson How connections are made Browser Internet
  • 31. @AaronGustafson Along comes Service Worker Browser Internet Cache
  • 32. @AaronGustafson Along comes Service Worker Browser Internet Cache !
  • 33. @AaronGustafson Along comes Service Worker Browser Internet Cache
  • 34. @AaronGustafson Know your (storage) limits Temporary Persistent Browser purges User purges
  • 35. @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
  • 36. Except on iOS.
 Safari gives you 50 MB.
  • 38. Storage is a privilege,
 don’t abuse it.
  • 39. How?
  • 41. @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
  • 42. @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
  • 43. @AaronGustafson #4: Provide alternate formats 43 <picture>
 <source type="image/webp" srcset="my.webp">
 <img src="my.jpg" alt="Alt text goes here">
 </picture>
  • 44. @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
  • 45. @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
  • 46. @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
  • 47. @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
  • 48. @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
  • 49. @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
  • 50. @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
  • 51. @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
  • 52. @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
  • 53. @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
  • 54. @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
  • 55. @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
  • 56. @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
  • 59. @AaronGustafson #6: Respect Save Data 58 let save_data = false;
 if ( 'connection' in navigator ) {
 save_data = navigator.connection.saveData;
 } aka.ms/ag-sw
  • 60. @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
  • 61. @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
  • 62. @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
  • 63. @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
  • 65. @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
  • 66. @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
  • 67. @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
  • 68. @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
  • 69. @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
  • 70. @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
  • 71. @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
  • 72. @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
  • 73. @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
  • 74. @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
  • 75. @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