Maxim Salnikov
@webmaxru
What makes an app feels
like native
Developer Audience Lead at Microsoft
~1,500,000 websites may be installable on mobile home screens, offering an app experience.
Reach
"Nativeness"
Native
Web
Web applications can reach anyone, anywhere, on any device with a single codebase.
Nativeness = Number & depth of integrations with the platform
Web App Manifest
Service Worker with "fetch"
{
{
"name": "BPM Techno",
"short_name": "BPM Techno Counter",
"start_url": "?utm_source=homescreen",
"display": "standalone",
"background_color": "#fff",
"description": "A free online BPM counter",
"icons": [{
"src": "images/touch/48x48.png",
"sizes": "48x48",
"type": "image/png"
}]
}
"shortcuts": [
{
"name": "Upload MP3 File",
"short_name": "Upload MP3r",
"description": "Count BPM of the uploaded file",
"url": "/upload-mp3?utm_source=homescreen",
"icons": [{ "src": "/icon-mp3.png", "sizes": "192x192" }]
}
]
"launch_handler": {
"route_to": "new-client" | "existing-client" |
"auto",
"navigate_existing_client": "always" | "never",
}
launchQueue.setConsumer(launchParams => {
const url = launchParams.targetURL;
});
{
"display": "fullscreen" | "standalone" |
"minimal-ui" | "browser",
"display_override": ["window-control-overlay",
"minimal-ui"],
}
titlebar-area-x
titlebar-area-y
titlebar-area-width
titlebar-area-height
navigator.windowControlsOverlay.
getBoundingClientRect()
navigator.windowControlsOverlay.visible
{
"display_override": "tabbed"
}
if (navigator.share) {
navigator.share({
title: 'BPM Techno',
text: 'Check out BPMTech.no',
url: 'https://bpmtech.no',
})
.then(() => console.log('Successful share'))
.catch((err) => console.error(err));
}
const props = ['name', 'email', 'tel'];
const opts = {multiple: true};
try {
const contacts =
await navigator.contacts.select(props, opts);
handleResults(contacts);
} catch (err) {
console.error(err));
}
let openFileHandle;
btnOpen.addEventListener('click', async () => {
[openFileHandle] =
await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const contents = await file.text();
});
const saveFileHandle =
await window.showSaveFilePicker();
const directoryHandle =
await window.showDirectoryPicker();
"url_handlers": [
{
"origin": "https://bpmtech.no"
},
{
"origin": "https://partnerapp.com"
}
]
"web_apps": [
{
"manifest": "https://partnerapp.com/manifest.json",
"details": {
"paths": [
"/public/data/*"
]
}
}
]
"capture_links":
"none" | "new-client" |
"existing-client-navigate"
"protocol_handlers": [
{
"protocol": "web+bpm",
"url": "index.html?bpm=%s"
}
]
web+bpm://bpm=120
"file_handlers": [
{
"action": "/open-mp3",
"accept": { "text/csv": [".mp3"] },
"icons": [
{
"src": "./images/mp3-file.png",
"sizes": "144x144"
}
]
}
]
window.launchQueue.setConsumer(launchParams => {
const fileHandle = launchParams.files[0];
});
"share_target": {
"action": "/share",
"method": "GET",
"params": {
"title": "title",
"text": "text",
"url": "url"
}
}
window.addEventListener('DOMContentLoaded', ()=> {
const parsedUrl = new URL(window.location);
const url = parsedUrl.searchParams.get('url');
});
navigator.setAppBadge(42).catch((err) => {
console.error(err)
});
navigator.clearAppBadge().catch((err) => {
console.error(err)
});