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
Media in the Age of PWAs [ImageCon 2019]
@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

Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...DianaGray10
 
UiPath Studio Web workshop series - Day 7
UiPath Studio Web workshop series - Day 7UiPath Studio Web workshop series - Day 7
UiPath Studio Web workshop series - Day 7DianaGray10
 
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...Aggregage
 
UiPath Studio Web workshop series - Day 6
UiPath Studio Web workshop series - Day 6UiPath Studio Web workshop series - Day 6
UiPath Studio Web workshop series - Day 6DianaGray10
 
KubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCost
KubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCostKubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCost
KubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCostMatt Ray
 
Machine Learning Model Validation (Aijun Zhang 2024).pdf
Machine Learning Model Validation (Aijun Zhang 2024).pdfMachine Learning Model Validation (Aijun Zhang 2024).pdf
Machine Learning Model Validation (Aijun Zhang 2024).pdfAijun Zhang
 
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdfUiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdfDianaGray10
 
NIST Cybersecurity Framework (CSF) 2.0 Workshop
NIST Cybersecurity Framework (CSF) 2.0 WorkshopNIST Cybersecurity Framework (CSF) 2.0 Workshop
NIST Cybersecurity Framework (CSF) 2.0 WorkshopBachir Benyammi
 
9 Steps For Building Winning Founding Team
9 Steps For Building Winning Founding Team9 Steps For Building Winning Founding Team
9 Steps For Building Winning Founding TeamAdam Moalla
 
Videogame localization & technology_ how to enhance the power of translation.pdf
Videogame localization & technology_ how to enhance the power of translation.pdfVideogame localization & technology_ how to enhance the power of translation.pdf
Videogame localization & technology_ how to enhance the power of translation.pdfinfogdgmi
 
Basic Building Blocks of Internet of Things.
Basic Building Blocks of Internet of Things.Basic Building Blocks of Internet of Things.
Basic Building Blocks of Internet of Things.YounusS2
 
AI Fame Rush Review – Virtual Influencer Creation In Just Minutes
AI Fame Rush Review – Virtual Influencer Creation In Just MinutesAI Fame Rush Review – Virtual Influencer Creation In Just Minutes
AI Fame Rush Review – Virtual Influencer Creation In Just MinutesMd Hossain Ali
 
Igniting Next Level Productivity with AI-Infused Data Integration Workflows
Igniting Next Level Productivity with AI-Infused Data Integration WorkflowsIgniting Next Level Productivity with AI-Infused Data Integration Workflows
Igniting Next Level Productivity with AI-Infused Data Integration WorkflowsSafe Software
 
How Accurate are Carbon Emissions Projections?
How Accurate are Carbon Emissions Projections?How Accurate are Carbon Emissions Projections?
How Accurate are Carbon Emissions Projections?IES VE
 
Designing A Time bound resource download URL
Designing A Time bound resource download URLDesigning A Time bound resource download URL
Designing A Time bound resource download URLRuncy Oommen
 
Nanopower In Semiconductor Industry.pdf
Nanopower  In Semiconductor Industry.pdfNanopower  In Semiconductor Industry.pdf
Nanopower In Semiconductor Industry.pdfPedro Manuel
 
OpenShift Commons Paris - Choose Your Own Observability Adventure
OpenShift Commons Paris - Choose Your Own Observability AdventureOpenShift Commons Paris - Choose Your Own Observability Adventure
OpenShift Commons Paris - Choose Your Own Observability AdventureEric D. Schabell
 
20230202 - Introduction to tis-py
20230202 - Introduction to tis-py20230202 - Introduction to tis-py
20230202 - Introduction to tis-pyJamie (Taka) Wang
 
Cybersecurity Workshop #1.pptx
Cybersecurity Workshop #1.pptxCybersecurity Workshop #1.pptx
Cybersecurity Workshop #1.pptxGDSC PJATK
 

Recently uploaded (20)

Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
 
UiPath Studio Web workshop series - Day 7
UiPath Studio Web workshop series - Day 7UiPath Studio Web workshop series - Day 7
UiPath Studio Web workshop series - Day 7
 
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
 
UiPath Studio Web workshop series - Day 6
UiPath Studio Web workshop series - Day 6UiPath Studio Web workshop series - Day 6
UiPath Studio Web workshop series - Day 6
 
KubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCost
KubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCostKubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCost
KubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCost
 
Machine Learning Model Validation (Aijun Zhang 2024).pdf
Machine Learning Model Validation (Aijun Zhang 2024).pdfMachine Learning Model Validation (Aijun Zhang 2024).pdf
Machine Learning Model Validation (Aijun Zhang 2024).pdf
 
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdfUiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
 
NIST Cybersecurity Framework (CSF) 2.0 Workshop
NIST Cybersecurity Framework (CSF) 2.0 WorkshopNIST Cybersecurity Framework (CSF) 2.0 Workshop
NIST Cybersecurity Framework (CSF) 2.0 Workshop
 
20230104 - machine vision
20230104 - machine vision20230104 - machine vision
20230104 - machine vision
 
9 Steps For Building Winning Founding Team
9 Steps For Building Winning Founding Team9 Steps For Building Winning Founding Team
9 Steps For Building Winning Founding Team
 
Videogame localization & technology_ how to enhance the power of translation.pdf
Videogame localization & technology_ how to enhance the power of translation.pdfVideogame localization & technology_ how to enhance the power of translation.pdf
Videogame localization & technology_ how to enhance the power of translation.pdf
 
Basic Building Blocks of Internet of Things.
Basic Building Blocks of Internet of Things.Basic Building Blocks of Internet of Things.
Basic Building Blocks of Internet of Things.
 
AI Fame Rush Review – Virtual Influencer Creation In Just Minutes
AI Fame Rush Review – Virtual Influencer Creation In Just MinutesAI Fame Rush Review – Virtual Influencer Creation In Just Minutes
AI Fame Rush Review – Virtual Influencer Creation In Just Minutes
 
Igniting Next Level Productivity with AI-Infused Data Integration Workflows
Igniting Next Level Productivity with AI-Infused Data Integration WorkflowsIgniting Next Level Productivity with AI-Infused Data Integration Workflows
Igniting Next Level Productivity with AI-Infused Data Integration Workflows
 
How Accurate are Carbon Emissions Projections?
How Accurate are Carbon Emissions Projections?How Accurate are Carbon Emissions Projections?
How Accurate are Carbon Emissions Projections?
 
Designing A Time bound resource download URL
Designing A Time bound resource download URLDesigning A Time bound resource download URL
Designing A Time bound resource download URL
 
Nanopower In Semiconductor Industry.pdf
Nanopower  In Semiconductor Industry.pdfNanopower  In Semiconductor Industry.pdf
Nanopower In Semiconductor Industry.pdf
 
OpenShift Commons Paris - Choose Your Own Observability Adventure
OpenShift Commons Paris - Choose Your Own Observability AdventureOpenShift Commons Paris - Choose Your Own Observability Adventure
OpenShift Commons Paris - Choose Your Own Observability Adventure
 
20230202 - Introduction to tis-py
20230202 - Introduction to tis-py20230202 - Introduction to tis-py
20230202 - Introduction to tis-py
 
Cybersecurity Workshop #1.pptx
Cybersecurity Workshop #1.pptxCybersecurity Workshop #1.pptx
Cybersecurity Workshop #1.pptx
 

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
  • 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