Entwicklung für moderne Browser und progressive Verbesserung wie im Jahr 2003
Im März 2003 verblüfften Nick Finck und Steve Champeon die Webdesign-Welt mit dem Konzept der progressiven Optimierung>. Bei dieser Webdesign-Strategie wird zuerst der Kerninhalt der Webseite geladen und dann nach und nach komplexere und technisch anspruchsvollere Ebenen für die Darstellung und Funktionen hinzugefügt. 2003 ging es bei Progressive Enhancement darum, moderne CSS-Funktionen, unaufdringliches JavaScript und sogar Scalable Vector Graphics zu verwenden. Bei der progressiven Optimierung geht es 2020 und darüber hinaus darum, moderne Browserfunktionen zu nutzen.

Modernes JavaScript
Apropos JavaScript: Die Browserunterstützung für die neuesten ES 2015-Kernfunktionen von JavaScript ist hervorragend.
Der neue Standard umfasst Promises, Module, Klassen, Template-Literale, Pfeilfunktionen, let
und const
, Standardparameter, Generatoren, die Destrukturierungszuweisung, Rest und Spread, Map
/Set
, WeakMap
/WeakSet
und vieles mehr.
Alle werden unterstützt.

Asynchrone Funktionen, eine ES2017-Funktion und einer meiner persönlichen Favoriten, können in allen wichtigen Browsern verwendet werden.
Mit den Schlüsselwörtern async
und await
kann asynchrones, auf Zusagen basierendes Verhalten in einem übersichtlicheren Stil geschrieben werden. Es ist nicht erforderlich, Zusagenketten explizit zu konfigurieren.

Und selbst die neuesten ES2020-Spracherweiterungen wie optional chaining und nullish coalescing werden sehr schnell unterstützt. Unten sehen Sie ein Codebeispiel. Was die wichtigsten JavaScript-Funktionen angeht, könnte es kaum besser laufen.
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah',
},
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0

Die Beispiel-App: Fugu Greetings
In diesem Artikel verwende ich eine einfache PWA namens Fugu Greetings (GitHub). Der Name dieser App ist eine Anspielung auf Project Fugu 🐡, mit dem das Web alle Funktionen von Android-, iOS- und Desktopanwendungen erhalten soll. Weitere Informationen zum Projekt
Fugu Greetings ist eine Zeichen-App, mit der Sie virtuelle Grußkarten erstellen und an Ihre Lieben senden können. Sie veranschaulicht die Kernkonzepte von PWAs. Sie ist zuverlässig und kann vollständig offline verwendet werden. Sie können sie also auch nutzen, wenn Sie keine Netzwerkverbindung haben. Sie kann auch auf dem Startbildschirm eines Geräts installiert werden und lässt sich als eigenständige Anwendung nahtlos in das Betriebssystem einfügen.

Progressive Verbesserung
Nachdem wir das geklärt haben, ist es an der Zeit, über Progressive Enhancement zu sprechen. Im MDN Web Docs-Glossar wird das Konzept so definiert:
Progressive Enhancement ist eine Designphilosophie, die möglichst vielen Nutzern eine Grundlage für wichtige Inhalte und Funktionen bietet und gleichzeitig nur Nutzern der modernsten Browser, die den gesamten erforderlichen Code ausführen können, die bestmögliche Nutzererfahrung bietet.
Funktionserkennung wird in der Regel verwendet, um festzustellen, ob Browser modernere Funktionen unterstützen. Polyfills werden häufig verwendet, um fehlende Funktionen mit JavaScript hinzuzufügen.
[…]
Progressive Enhancement ist eine nützliche Technik, mit der sich Webentwickler auf die Entwicklung der bestmöglichen Websites konzentrieren können und gleichzeitig dafür sorgen, dass diese Websites auf mehreren unbekannten User-Agents funktionieren. Graceful Degradation (sanfte Herabstufung) ist verwandt, aber nicht dasselbe und wird oft als das Gegenteil von Progressive Enhancement angesehen. In der Realität sind beide Ansätze gültig und können sich oft ergänzen.
MDN-Beitragende
Jede Grußkarte von Grund auf neu zu erstellen, kann sehr mühsam sein.
Warum gibt es also keine Funktion, mit der Nutzer ein Bild importieren und darauf aufbauen können?
Bei einem herkömmlichen Ansatz hätten Sie dazu ein <input type=file>
-Element verwendet.
Zuerst erstellen Sie das Element, legen type
auf 'file'
fest und fügen der Eigenschaft accept
MIME-Typen hinzu. Dann „klicken“ Sie programmatisch darauf und warten auf Änderungen.
Wenn Sie ein Bild auswählen, wird es direkt auf die Arbeitsfläche importiert.
const importImage = async () => {
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.addEventListener('change', () => {
resolve(input.files[0]);
});
input.click();
});
};
Wenn es eine Importfunktion gibt, sollte es wahrscheinlich auch eine Exportfunktion geben, damit Nutzer ihre Grußkarten lokal speichern können.
Dateien werden normalerweise gespeichert, indem ein Ankerlink mit einem download
-Attribut und einer Blob-URL als href
erstellt wird.
Sie würden auch programmatisch darauf „klicken“, um den Download auszulösen, und hoffentlich nicht vergessen, die Blob-Objekt-URL zu widerrufen, um Speicherlecks zu vermeiden.
const exportImage = async (blob) => {
const a = document.createElement('a');
a.download = 'fugu-greeting.png';
a.href = URL.createObjectURL(blob);
a.addEventListener('click', (e) => {
setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
});
a.click();
};
Aber warte mal. Sie haben die Grußkarte nicht „heruntergeladen“, sondern „gespeichert“. Anstatt ein Dialogfeld zum Speichern anzuzeigen, in dem Sie auswählen können, wo die Datei gespeichert werden soll, hat der Browser die Grußkarte direkt ohne Nutzerinteraktion heruntergeladen und in Ihrem Downloadordner gespeichert. Das ist nicht gut.
Was wäre, wenn es eine bessere Möglichkeit gäbe? Was wäre, wenn Sie einfach eine lokale Datei öffnen, bearbeiten und die Änderungen dann in einer neuen Datei oder in der Originaldatei speichern könnten? Das ist möglich. Mit der File System Access API können Sie Dateien und Verzeichnisse öffnen und erstellen sowie ändern und speichern .
Wie kann ich also eine API erkennen?
Die File System Access API bietet eine neue Methode: window.chooseFileSystemEntries()
.
Daher muss ich je nach Verfügbarkeit dieser Methode verschiedene Import- und Exportmodule bedingt laden. Unten sehen Sie, wie das geht.
const loadImportAndExport = () => {
if ('chooseFileSystemEntries' in window) {
Promise.all([
import('./import_image.mjs'),
import('./export_image.mjs'),
]);
} else {
Promise.all([
import('./import_image_legacy.mjs'),
import('./export_image_legacy.mjs'),
]);
}
};
Bevor ich jedoch auf die Details der File System Access API eingehe, möchte ich kurz auf das Muster für die progressive Verbesserung eingehen. In Browsern, die die File System Access API derzeit nicht unterstützen, lade ich die alten Skripts. Unten sehen Sie die Netzwerk-Tabs von Firefox und Safari.


In Chrome, einem Browser, der die API unterstützt, werden jedoch nur die neuen Skripts geladen.
Das ist dank dynamischer import()
möglich, die von allen modernen Browsern unterstützt werden.
Wie ich schon sagte, ist das Gras heutzutage ziemlich grün.

File System Access API
Nachdem ich das geklärt habe, ist es an der Zeit, sich die eigentliche Implementierung auf Grundlage der File System Access API anzusehen.
Zum Importieren eines Bildes rufe ich window.chooseFileSystemEntries()
auf und übergebe ihm das Attribut accepts
, in dem ich angebe, dass ich Bilddateien möchte.
Sowohl Dateiendungen als auch MIME-Typen werden unterstützt.
Daraus ergibt sich ein Dateihandle, über das ich die eigentliche Datei durch Aufrufen von getFile()
abrufen kann.
const importImage = async () => {
try {
const handle = await window.chooseFileSystemEntries({
accepts: [
{
description: 'Image files',
mimeTypes: ['image/*'],
extensions: ['jpg', 'jpeg', 'png', 'webp', 'svg'],
},
],
});
return handle.getFile();
} catch (err) {
console.error(err.name, err.message);
}
};
Das Exportieren eines Bildes ist fast dasselbe, aber dieses Mal muss ich einen Typparameter von 'save-file'
an die Methode chooseFileSystemEntries()
übergeben.
Daraufhin wird ein Dialogfeld zum Speichern der Datei angezeigt.
Wenn die Datei geöffnet ist, ist das nicht erforderlich, da 'open-file'
die Standardeinstellung ist.
Ich lege den Parameter accepts
ähnlich wie zuvor fest, diesmal jedoch nur für PNG-Bilder.
Ich erhalte wieder ein Dateihandle, aber anstatt die Datei abzurufen, erstelle ich dieses Mal einen beschreibbaren Stream, indem ich createWritable()
aufrufe.
Als Nächstes schreibe ich den Blob, also das Bild für die Grußkarte, in die Datei.
Schließlich schließe ich den beschreibbaren Stream.
Es kann immer etwas schiefgehen: Die Festplatte könnte voll sein, es könnte ein Schreib- oder Lesefehler auftreten oder der Nutzer bricht das Dateidialogfeld einfach ab.
Deshalb umschließe ich die Aufrufe immer mit einer try...catch
-Anweisung.
const exportImage = async (blob) => {
try {
const handle = await window.chooseFileSystemEntries({
type: 'save-file',
accepts: [
{
description: 'Image file',
extensions: ['png'],
mimeTypes: ['image/png'],
},
],
});
const writable = await handle.createWritable();
await writable.write(blob);
await writable.close();
} catch (err) {
console.error(err.name, err.message);
}
};
Durch die Verwendung von Progressive Enhancement mit der File System Access API kann ich eine Datei wie zuvor öffnen. Die importierte Datei wird direkt auf den Canvas gezeichnet. Ich kann meine Änderungen vornehmen und sie dann in einem echten Speicherdialogfeld speichern, in dem ich den Namen und den Speicherort der Datei auswählen kann. Die Datei kann jetzt für die Ewigkeit aufbewahrt werden.



Die Web Share und Web Share Target APIs
Vielleicht möchte ich meine Grußkarte ja nicht nur für die Ewigkeit speichern, sondern auch teilen. Das ist mit der Web Share API und der Web Share Target API möglich. Mobile und in letzter Zeit auch Desktop-Betriebssysteme haben integrierte Freigabemechanismen erhalten. Unten sehen Sie beispielsweise das Freigabeblatt von Safari für Computer unter macOS, das über einen Artikel in meinem Blog aufgerufen wurde. Wenn Sie auf die Schaltfläche Artikel teilen klicken, können Sie einen Link zum Artikel mit einem Freund teilen, z. B. über die macOS-App „Nachrichten“.

Der Code dafür ist recht einfach. Ich rufe navigator.share()
auf und übergebe ein optionales title
, text
und url
in einem Objekt.
Was ist aber, wenn ich ein Bild anhängen möchte? Level 1 der Web Share API unterstützt dies noch nicht.
Die gute Nachricht ist, dass mit Web Share Level 2 die Möglichkeit zur Dateifreigabe hinzugekommen ist.
try {
await navigator.share({
title: 'Check out this article:',
text: `"${document.title}" by @tomayac:`,
url: document.querySelector('link[rel=canonical]').href,
});
} catch (err) {
console.warn(err.name, err.message);
}
Ich zeige Ihnen, wie Sie das mit der Fugu-Anwendung für Grußkarten umsetzen können.
Zuerst muss ich ein data
-Objekt mit einem files
-Array mit einem Blob und dann ein title
und ein text
vorbereiten. Als Nächstes verwende ich als Best Practice die neue Methode navigator.canShare()
, die genau das tut, was ihr Name vermuten lässt: Sie gibt an, ob das data
-Objekt, das ich freigeben möchte, technisch vom Browser freigegeben werden kann.
Wenn navigator.canShare()
mir mitteilt, dass die Daten geteilt werden können, bin ich bereit, navigator.share()
wie zuvor aufzurufen.
Da alles fehlschlagen kann, verwende ich wieder einen try...catch
-Block.
const share = async (title, text, blob) => {
const data = {
files: [
new File([blob], 'fugu-greeting.png', {
type: blob.type,
}),
],
title: title,
text: text,
};
try {
if (!(navigator.canShare(data))) {
throw new Error("Can't share data.", data);
}
await navigator.share(data);
} catch (err) {
console.error(err.name, err.message);
}
};
Wie zuvor verwende ich Progressive Enhancement.
Wenn sowohl 'share'
als auch 'canShare'
im navigator
-Objekt vorhanden sind, fahre ich fort und lade share.mjs
über dynamisches import()
.
In Browsern wie Mobile Safari, die nur eine der beiden Bedingungen erfüllen, wird die Funktion nicht geladen.
const loadShare = () => {
if ('share' in navigator && 'canShare' in navigator) {
import('./share.mjs');
}
};
Wenn ich in Fugu Greetings in einem unterstützten Browser wie Chrome unter Android auf die Schaltfläche Teilen tippe, wird das integrierte Freigabeblatt geöffnet. Ich kann beispielsweise Gmail auswählen. Das Widget zum Verfassen von E‑Mails wird dann mit dem angehängten Bild angezeigt.


Contact Picker API
Als Nächstes möchte ich über Kontakte sprechen, also das Adressbuch eines Geräts oder die Kontakte-App. Wenn Sie eine Grußkarte schreiben, ist es möglicherweise nicht immer einfach, den Namen einer Person richtig zu schreiben. Ich habe zum Beispiel einen Freund namens Sergej, der seinen Namen lieber in kyrillischen Buchstaben geschrieben sieht. Ich verwende eine deutsche QWERTZ-Tastatur und weiß nicht, wie ich ihren Namen eingeben soll. Dieses Problem lässt sich mit der Contact Picker API lösen. Da ich meinen Freund in der Kontakte App auf meinem Smartphone gespeichert habe, kann ich über die Contacts Picker API über das Web auf meine Kontakte zugreifen.
Zuerst muss ich die Liste der Eigenschaften angeben, auf die ich zugreifen möchte.
In diesem Fall möchte ich nur die Namen, aber für andere Anwendungsfälle bin ich möglicherweise an Telefonnummern, E‑Mail-Adressen, Avatarsymbolen oder physischen Adressen interessiert.
Als Nächstes konfiguriere ich ein options
-Objekt und setze multiple
auf true
, damit ich mehr als einen Eintrag auswählen kann.
Schließlich kann ich navigator.contacts.select()
aufrufen, um die gewünschten Eigenschaften für die vom Nutzer ausgewählten Kontakte abzurufen.
const getContacts = async () => {
const properties = ['name'];
const options = { multiple: true };
try {
return await navigator.contacts.select(properties, options);
} catch (err) {
console.error(err.name, err.message);
}
};
Wahrscheinlich haben Sie das Muster inzwischen verstanden: Ich lade die Datei nur, wenn die API tatsächlich unterstützt wird.
if ('contacts' in navigator) {
import('./contacts.mjs');
}
Wenn ich in Fugu Greeting auf die Schaltfläche Kontakte tippe und meine beiden besten Freunde auswähle, Сергей Михайлович Брин und 劳伦斯·爱德华·"拉里"·佩奇, sehen Sie, dass die Kontaktauswahl nur ihre Namen anzeigt, nicht aber ihre E-Mail-Adressen oder andere Informationen wie ihre Telefonnummern. Ihre Namen werden dann auf meine Grußkarte gezeichnet.


Die Asynchronous Clipboard API
Als Nächstes geht es um das Kopieren und Einfügen. Als Softwareentwickler nutzen wir die Funktion „Kopieren und Einfügen“ besonders gern. Als Autor von Grußkarten möchte ich das manchmal auch tun. Ich möchte entweder ein Bild in eine Grußkarte einfügen, an der ich gerade arbeite, oder meine Grußkarte kopieren, damit ich sie an einem anderen Ort weiter bearbeiten kann. Die Async Clipboard API unterstützt sowohl Text als auch Bilder. Ich zeige dir, wie ich die Unterstützung für das Kopieren und Einfügen in die FuguGreetings-App eingebaut habe.
Damit ich etwas in die Zwischenablage des Systems kopieren kann, muss ich darauf zugreifen.
Die Methode navigator.clipboard.write()
verwendet ein Array von Zwischenablageelementen als Parameter.
Jedes Zwischenablageelement ist im Grunde ein Objekt mit einem Blob als Wert und dem Typ des Blobs als Schlüssel.
const copy = async (blob) => {
try {
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
}),
]);
} catch (err) {
console.error(err.name, err.message);
}
};
Zum Einfügen muss ich die Zwischenablageelemente durchlaufen, die ich durch Aufrufen von navigator.clipboard.read()
erhalte.
Das liegt daran, dass sich mehrere Zwischenablageelemente in unterschiedlichen Darstellungen in der Zwischenablage befinden können.
Jeder Zwischenablageeintrag hat ein Feld types
, das mir die MIME-Typen der verfügbaren Ressourcen angibt.
Ich rufe die getType()
-Methode des Zwischenablageelements auf und übergebe den MIME-Typ, den ich zuvor abgerufen habe.
const paste = async () => {
try {
const clipboardItems = await navigator.clipboard.read();
for (const clipboardItem of clipboardItems) {
try {
for (const type of clipboardItem.types) {
const blob = await clipboardItem.getType(type);
return blob;
}
} catch (err) {
console.error(err.name, err.message);
}
}
} catch (err) {
console.error(err.name, err.message);
}
};
Das muss ich eigentlich nicht mehr erwähnen. Ich mache das nur in unterstützten Browsern.
if ('clipboard' in navigator && 'write' in navigator.clipboard) {
import('./clipboard.mjs');
}
Wie funktioniert das in der Praxis? Ich habe ein Bild in der macOS-App „Vorschau“ geöffnet und kopiere es in die Zwischenablage. Wenn ich auf Einfügen klicke, fragt die Fugu Greetings App mich, ob ich der App erlauben möchte, Text und Bilder in der Zwischenablage zu sehen.

Nachdem Sie die Berechtigung erteilt haben, wird das Bild in die Anwendung eingefügt. Das funktioniert auch andersherum. Kopiere eine Grußkarte in die Zwischenablage. Wenn ich dann die Vorschau öffne und auf Datei und dann auf Neu aus Zwischenablage klicke, wird die Grußkarte in ein neues, unbenanntes Bild eingefügt.

Die Badging API
Eine weitere nützliche API ist die Badging API.
Als installierbare PWA hat Fugu Greetings natürlich ein App-Symbol, das Nutzer im App-Dock oder auf dem Startbildschirm platzieren können.
Eine unterhaltsame und einfache Möglichkeit, die API zu demonstrieren, besteht darin, sie in Fugu Greetings als Strichzähler zu verwenden.
Ich habe einen Event-Listener hinzugefügt, der den Zähler für Stiftstriche erhöht, wenn das pointerdown
-Ereignis eintritt, und dann das aktualisierte Symbol-Badge festlegt.
Wenn das Canvas gelöscht wird, wird der Zähler zurückgesetzt und das Badge entfernt.
let strokes = 0;
canvas.addEventListener('pointerdown', () => {
navigator.setAppBadge(++strokes);
});
clearButton.addEventListener('click', () => {
strokes = 0;
navigator.setAppBadge(strokes);
});
Diese Funktion ist eine progressive Erweiterung, daher ist die Ladereihenfolge wie gewohnt.
if ('setAppBadge' in navigator) {
import('./badge.mjs');
}
In diesem Beispiel habe ich die Zahlen von eins bis sieben mit einem Strich pro Zahl gezeichnet. Die Anzahl der Badges auf dem Symbol ist jetzt sieben.


Die Periodic Background Sync API
Möchtest du jeden Tag mit etwas Neuem beginnen? Eine praktische Funktion der Fugu Greetings App ist, dass sie Sie jeden Morgen mit einem neuen Hintergrundbild für Ihre Grußkarte inspirieren kann. Dazu verwendet die App die Periodic Background Sync API.
Der erste Schritt besteht darin, ein Ereignis für die regelmäßige Synchronisierung in der Service Worker-Registrierung zu registrieren.
Es wird auf ein Synchronisierungstag namens 'image-of-the-day'
gewartet und es gibt ein Mindestintervall von einem Tag, sodass der Nutzer alle 24 Stunden ein neues Hintergrundbild erhält.
const registerPeriodicBackgroundSync = async () => {
const registration = await navigator.serviceWorker.ready;
try {
registration.periodicSync.register('image-of-the-day-sync', {
// An interval of one day.
minInterval: 24 * 60 * 60 * 1000,
});
} catch (err) {
console.error(err.name, err.message);
}
};
Im zweiten Schritt warten Sie im Service Worker auf das periodicsync
-Ereignis.
Wenn das Ereignis-Tag 'image-of-the-day'
ist, also das, das zuvor registriert wurde, wird das Bild des Tages über die Funktion getImageOfTheDay()
abgerufen und das Ergebnis an alle Clients weitergegeben, damit sie ihre Arbeitsbereiche und Caches aktualisieren können.
self.addEventListener('periodicsync', (syncEvent) => {
if (syncEvent.tag === 'image-of-the-day-sync') {
syncEvent.waitUntil(
(async () => {
const blob = await getImageOfTheDay();
const clients = await self.clients.matchAll();
clients.forEach((client) => {
client.postMessage({
image: blob,
});
});
})()
);
}
});
Auch hier handelt es sich um eine progressive Erweiterung. Der Code wird also nur geladen, wenn die API vom Browser unterstützt wird.
Das gilt sowohl für den Client- als auch für den Service Worker-Code.
In Browsern, die das nicht unterstützen, wird keines der beiden geladen.
Beachten Sie, dass im Service Worker anstelle einer dynamischen import()
(die in einem Service Worker-Kontext noch nicht unterstützt wird) die klassische importScripts()
verwendet wird.
// In the client:
const registration = await navigator.serviceWorker.ready;
if (registration && 'periodicSync' in registration) {
import('./periodic_background_sync.mjs');
}
// In the service worker:
if ('periodicSync' in self.registration) {
importScripts('./image_of_the_day.mjs');
}
Wenn Sie in Fugu Greetings auf die Schaltfläche Hintergrund klicken, wird das Grußkartenbild des Tages angezeigt, das täglich über die Periodic Background Sync API aktualisiert wird.

Notification Triggers API
Manchmal braucht es einen kleinen Anstoß, um eine begonnene Grußkarte fertigzustellen, auch wenn man viele Ideen hat. Diese Funktion wird durch die Notification Triggers API aktiviert. Als Nutzer kann ich eine Zeit eingeben, zu der ich daran erinnert werden möchte, meine Grußkarte fertigzustellen. Wenn es so weit ist, erhalte ich eine Benachrichtigung, dass meine Grußkarte bereitliegt.
Nachdem die Zielzeit abgefragt wurde, plant die Anwendung die Benachrichtigung mit einem showTrigger
.
Das kann ein TimestampTrigger
mit dem zuvor ausgewählten Zieldatum sein.
Die Erinnerungsbenachrichtigung wird lokal ausgelöst. Es ist kein Netzwerk oder keine Serverseite erforderlich.
const targetDate = promptTargetDate();
if (targetDate) {
const registration = await navigator.serviceWorker.ready;
registration.showNotification('Reminder', {
tag: 'reminder',
body: "It's time to finish your greeting card!",
showTrigger: new TimestampTrigger(targetDate),
});
}
Wie bei allem, was ich bisher gezeigt habe, handelt es sich um eine progressive Verbesserung. Der Code wird also nur bedingt geladen.
if ('Notification' in window && 'showTrigger' in Notification.prototype) {
import('./notification_triggers.mjs');
}
Wenn ich in Fugu Greetings das Kästchen Erinnerung aktiviere, werde ich gefragt, wann ich daran erinnert werden möchte, meine Grußkarte fertigzustellen.

Wenn eine geplante Benachrichtigung in Fugu Greetings ausgelöst wird, wird sie wie jede andere Benachrichtigung angezeigt. Wie bereits erwähnt, ist dafür jedoch keine Netzwerkverbindung erforderlich.

Die Wake Lock API
Ich möchte auch die Wake Lock API einbinden. Manchmal muss man nur lange genug auf den Bildschirm starren, bis die Inspiration einen küsst. Im schlimmsten Fall schaltet sich der Bildschirm aus. Die Wake Lock API kann dies verhindern.
Im ersten Schritt muss mit navigator.wakelock.request method()
ein Wake Lock abgerufen werden.
Ich übergebe den String 'screen'
, um ein Wake Lock für den Bildschirm zu erhalten.
Anschließend füge ich einen Event-Listener hinzu, um benachrichtigt zu werden, wenn die Wake Lock-Sperre aufgehoben wird.
Das kann beispielsweise passieren, wenn sich die Sichtbarkeit des Tabs ändert.
Wenn das passiert, kann ich das Wake Lock wieder abrufen, sobald der Tab wieder sichtbar wird.
let wakeLock = null;
const requestWakeLock = async () => {
wakeLock = await navigator.wakeLock.request('screen');
wakeLock.addEventListener('release', () => {
console.log('Wake Lock was released');
});
console.log('Wake Lock is active');
};
const handleVisibilityChange = () => {
if (wakeLock !== null && document.visibilityState === 'visible') {
requestWakeLock();
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
document.addEventListener('fullscreenchange', handleVisibilityChange);
Ja, das ist eine progressive Erweiterung. Ich muss sie also nur laden, wenn der Browser die API unterstützt.
if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
import('./wake_lock.mjs');
}
In Fugu Greetings gibt es das Kästchen Insomnia. Wenn es aktiviert ist, bleibt der Bildschirm aktiv.

Idle Detection API
Manchmal kann es passieren, dass Sie stundenlang auf den Bildschirm starren und Ihnen einfach nichts einfällt, was Sie auf die Grußkarte schreiben könnten. Mit der Idle Detection API kann die App die Inaktivität des Nutzers erkennen. Wenn der Nutzer zu lange inaktiv ist, wird die App auf den ursprünglichen Zustand zurückgesetzt und die Leinwand wird geleert. Der Zugriff auf diese API ist derzeit auf die Berechtigung für Benachrichtigungen beschränkt, da viele Produktionsanwendungsfälle der Inaktivitätserkennung mit Benachrichtigungen zusammenhängen, z. B. um eine Benachrichtigung nur an ein Gerät zu senden, das der Nutzer gerade aktiv verwendet.
Nachdem ich sichergestellt habe, dass die Berechtigung für Benachrichtigungen erteilt wurde, instanziere ich den Leerlauferkennungsdienst. Ich registriere einen Event-Listener, der auf Änderungen im Leerlauf wartet. Dazu gehören der Nutzer- und der Bildschirmstatus. Der Nutzer kann aktiv oder inaktiv sein und der Bildschirm kann entsperrt oder gesperrt sein. Wenn der Nutzer inaktiv ist, wird das Canvas geleert. Ich gebe dem Inaktivitätserkennungstool einen Grenzwert von 60 Sekunden.
const idleDetector = new IdleDetector();
idleDetector.addEventListener('change', () => {
const userState = idleDetector.userState;
const screenState = idleDetector.screenState;
console.log(`Idle change: ${userState}, ${screenState}.`);
if (userState === 'idle') {
clearCanvas();
}
});
await idleDetector.start({
threshold: 60000,
signal,
});
Wie immer lade ich diesen Code nur, wenn der Browser ihn unterstützt.
if ('IdleDetector' in window) {
import('./idle_detection.mjs');
}
In der Fugu Greetings App wird der Canvas gelöscht, wenn das Kästchen Ephemeral angeklickt ist und der Nutzer zu lange inaktiv ist.

Abschluss
Puh, was für eine Fahrt. So viele APIs in nur einer Beispiel-App. Und denken Sie daran: Ich lasse den Nutzer nie für den Download einer Funktion bezahlen, die sein Browser nicht unterstützt. Durch Progressive Enhancement sorge ich dafür, dass nur der relevante Code geladen wird. Da Anfragen mit HTTP/2 kostengünstig sind, sollte dieses Muster für viele Anwendungen gut funktionieren. Bei sehr großen Anwendungen sollten Sie jedoch einen Bundler in Betracht ziehen.

Die App sieht in jedem Browser möglicherweise etwas anders aus, da nicht alle Plattformen alle Funktionen unterstützen. Die Kernfunktionen sind jedoch immer vorhanden und werden entsprechend den Möglichkeiten des jeweiligen Browsers erweitert. Diese Funktionen können sich auch in ein und demselben Browser ändern, je nachdem, ob die App als installierte App oder in einem Browser-Tab ausgeführt wird.



Wenn Sie sich für die App Fugu Greetings interessieren, suchen Sie sie auf GitHub und forken Sie sie.

Das Chromium-Team arbeitet intensiv daran, die Situation bei erweiterten Fugu-APIs zu verbessern. Durch die Anwendung von Progressive Enhancement bei der Entwicklung meiner App sorge ich dafür, dass alle Nutzer eine gute, solide Baseline-Erfahrung erhalten, aber dass Nutzer von Browsern, die mehr Webplattform-APIs unterstützen, eine noch bessere Erfahrung erhalten. Ich bin gespannt, wie Sie Progressive Enhancement in Ihren Apps einsetzen.
Danksagungen
Ich bin Christian Liebel und Hemanth HM dankbar, die beide zu Fugu Greetings beigetragen haben.
Dieser Artikel wurde von Joe Medley und Kayce Basques geprüft.
Jake Archibald hat mir geholfen, die Situation mit dynamischen import()
in einem Service Worker-Kontext herauszufinden.