high fidelity
prototypes
surpassing the uncanny valley
Target UX
- Mobile or desktop app
- Launch from coveted system drawer
- Load instant & full screen
- Seamless demo flow
Why?
- User testing
- Get funding
- Sell a client
UX flow worth 1k words
Prototype worth 1k meetings
How
- A manifest
- Static assets
- The Web
optimize for first paint
Rewind
Layer by layer let's enhance a prototype
Manifest
PWA entry point
Load It
<link rel="manifest" href="/manifest.json">
App Name
{
"name": "YOUR_APP",
"short_name": "YOUR_APP",
}
Used on device homescreen
App Theme
{
"theme_color": "#ffffff",
"background_color": "#ffffff",
}
OS tints
App Icons
{
"icons": [
{
"src": "/icons/192x192.png",
"sizes": "192x192",
"type": "image/png",
"density": "4.0"
},
...
]
}
PWA Extras
{
"display": "standalone",
"orientation": "portrait",
"start_url": "/index.html",
"scope": "/",
}
Verify
Chrome Devtools can verify your work before you install on a device
<meta>
Inform the render engine
Viewport
<meta name="viewport" content="initial-scale=1" />
- Page width to device width
- Remove 300ms tap delay
iOS Web App
<meta name="mobile-web-app-capable" content="yes" />
<meta name="application-name" content="YOUR_APP" />
- Legacy?
- Fullscreen
- Links need love
App Theme
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="theme-color" content="#ffffff" />
<meta name="msapplication-TileColor" content="#ffffff" />
App Brand
<link rel="icon" sizes="192x192" href="/icons/192.png" />
<link rel="apple-touch-icon" href="/icons/apple-icon.png" />
<link rel="apple-touch-startup-image" href="/icons/icons/apple-splash.png" />
<meta name="msapplication-TileImage" content="/icons/ms-icon.png" />
OS icons & launch assets
Icons
1st Impressions
Tips n' Tricks
- Generator sites
- Build scripts
- Minimalism
- Bespoke
Beware: icons can quickly become debt
Splash
2nd Impressions
iOS Tips n' Tricks
- Generator sites
- Build scripts
Tips n' Tricks
- Manifest + icons
- Not in full control
Prerender
SSR, compile, build, etc
Why?
- Limit requests
- Empower preload/prefetch/cache
- Skip tombstones / skeletons
- Spinnerless
- On rails
Goal
Maintain an illusion,
not build for production
JAMstack to SPA (or JAMSPA 😏) are valid HTML experience creation options
Bundle
Like prerendered dependencies
Evaluate
Depending on the complexity of your prototype, you may have 0 to very little Javascript
Prioritize
- Critical vs Non-critical
- This before that
Chunk
Evaluation and prioritization are heavy informers on what bundle chunk opportunities exist
Differential Serving
- JS
- CSS
HTTPS
Native gateway
HTML5
- Web share
- Geolocation
- Motion sensors
- Camera access
- and more...
PWA requirement
Not just a blocker to features, it's a requirement for promotion
Secure
You should want this
Preload
a declarative fetch directive
Preload
Cache ASAP, NO execution
<link rel="preload" href="..." as="style">
<link rel="preload" href="..." as="font" type="font/woff2" crossorigin>
<link rel="preload" href="..." as="script">
<link rel="preload" href="..." as="image">
NO delaying the page's onload
Good for:
- Above the fold
- Critical CSS
- Critical fonts
- Critical graphics
- Critical scripts?
Prefetch
Lower priority items
<link rel="prefetch" href="..." as="style">
<link rel="prefetch" href="..." as="font" type="font/woff2" crossorigin>
<link rel="prefetch" href="..." as="script">
<link rel="prefetch" href="..." as="image">
Fetch while chillin
DNS Prefetch
Known off origin deps
<link rel="dns-prefetch" href="...">
<link rel="preconnect" href="...">
3rd party dependencies?
Media Query
conditional preload
<link rel="preload" as="style" href="..." media="(orientation: portrait)">
<link rel="preload" as="script" href="..." media="(min-width: 1200px)">
Prerender Precache
all page resources
importScripts('.../workbox-sw.js');
workbox.precaching.precacheAndRoute([
'index.html',
'/common.bundle.js',
'/app.bundle.js',
'/app.bundle.css',
'/images/hero.png',
...
])
Defer
no render blocking here
Fetch These
asynchronously OR deferred
<script defer src="..."></script>
<script src="..." type="module"></script>
<script async src="..." type="module"></script>
<link rel="preload" as="style" href="..."
onload="this.rel='stylesheet'">
non-blocking resource loading
defer
Load async, then =>
execute in the order recieved
<script defer src="..."></script>
<script src="..." type="module"></script>
async
Load async, then =>
execute immediately
<script async src="..."></script>
<script async src="..." type="module"></script>
async CSS
Load async, then =>
apply immediately
<link rel="preload" as="style" href="..."
onload="this.rel='stylesheet'">
service worker
Cache money
static files
// service-worker.js
workbox.routing.registerRoute(
/\.(?:js|css)$/,
new workbox.strategies.StaleWhileRevalidate(),
);
media files
// service-worker.js
workbox.routing.registerRoute(
/\.(?:png|gif|jpg|jpeg|svg)$/,
new workbox.strategies.CacheFirst({
cacheName: 'images',
}),
);
dynamic files
🤷♂️
push notifications
const localPush = async () => {
const swRegistration = await navigator.serviceWorker.register('...')
swRegistration.showNotification('...', {
body, icon, image, vibrate, // etc...
});
}
Can add a nice touch
Lazy Load
Optimize for first paint
Lazy Hooks
- Time
- Events
- Hints
Lazy Candidates
- Uncriticals
- Nice to have glyphs
- Interaction required components
- 3rd party
Lazy Tactics
- Libs
- Dynamic import
- Attributes
<img loading="lazy" src="...">
<iframe loading="lazy" src="...">
Paint Holding
Chromium freeby
Without
With
nice to have
not critical, we've covered our bases
more benefits for !prototypes, but worth talking about
Fonts
tips n tricks
System Fonts
body {
font-family: system-ui,
-apple-system, Segoe UI,
Roboto, Ubuntu, Cantarell,
Noto Sans, sans-serif,
Segoe UI, Roboto, Ubuntu,
Cantarell, Noto Sans,
sans-serif;
}
Boot faster & look native
Custom Fonts
Leverage preload and caching
Consider front loading
Variable Fonts
1 font to rule them all
Lazy Load
Non critical glyphs or whole families of a font can be loaded after the intial page load
CSS
Magic polish
Detection
@media (display-mode: standalone) {
...
}
Adjustments only if
Scrollviews
get that native scroll
.scrollview {
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
Text Selection
body {
-webkit-user-select: none;
}
Tap Highlight
body {
-webkit-tap-highlight-color: transparent;
}
Callouts
body {
-webkit-touch-callout: none;
}
Hover & Active
<html>
<head>...</head>
<body ontouchstart>
...
</body>
</html>
Sticky
header {
position: sticky;
top: 0;
}
Snap Points
.filmstrip {
overflow-x: scroll;
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
& > * {
scroll-snap-align: center;
}
}
<img />
Set sizes inline
<img src="logo.png" height="50px" />
Jank Prevention: jumpless
Retina
Leverage that DPI
Prepare
optimize › crunch › process
host on the edge
<picture>
<picture>
<source type="image/webp" srcset="...@2x.webp 2x, ...@1x.webp 1x"/>
<source type="image/png" srcset="...@2x.png 2x, ...@1x.png 1x"/>
<img height="200" src="..." alt="..." loading="lazy">
</picture>
let the browser pick the best
Gotchas
- Debt
- Heavy
- Performance
Pull 2 Refresh
Can ruin the illusion
Chromium
.scrollview {
overscroll-behavior-y: contain;
}
Keep the glow & stop navigational features
iOS Links
Stay in app...
A new window? Really?
really really.
Stay in app
{
"name": "Coinsafe",
"short_name": "Coinsafe",
"start_url": "/index.html"
}
load the destination within this webview
Offline UX
show your work
const { isOnline } = navigator
Listen
window.addEventListener('online', e =>
console.info('online'))
window.addEventListener('offline', e =>
console.info('offline'))
Lie-Fi
beware
UI
nice polish
that was..
18 steps
to a seamless PWototype
- manifest.json
- <meta>
- icons
- splash
- bundle
- HTTPS
- defer
- preload
- precache
- service workers
- lazy load
- paint holding
- fonts
- CSS
- retina
- pull to refresh
- iOS links
- offline UX
Healthy Apps
these implementation details are good for any project