Entwicklung für moderne Browser und progressive Verbesserung wie im Jahr 2003
Veröffentlicht: 29. Juni 2020
Im März 2003 stellten Nick Finck und Steve Champeon das Konzept der progressiven Verbesserung> vor, eine Strategie für Webdesign, bei der zuerst die wichtigsten Inhalte der Webseite geladen werden und dann nach und nach anspruchsvollere und technisch anspruchsvollere Ebenen der Präsentation und Funktionen hinzugefügt werden. 2003 ging es bei Progressive Enhancement darum, moderne CSS-Funktionen, unaufdringliches JavaScript und sogar Scalable Vector Graphics zu verwenden. Bei Progressive Enhancement 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 Sprachzusätze von ES 2020 wie optional chaining und nullish coalescing werden sehr schnell unterstützt. Was die wichtigsten JavaScript-Funktionen angeht, ist es kaum zu übertreffen.
Beispiel:
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 Dokument verwende ich eine PWA namens Fugu Greetings (GitHub). Der Name dieser App ist eine Anspielung auf Project Fugu 🐡, eine Initiative, die dem Web alle Funktionen von Android-, iOS- und Desktop-Anwendungen geben 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 Verbesserung 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 in den Arbeitsbereich 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.
Die herkömmliche Methode zum Speichern von Dateien besteht darin, einen Ankerlink mit einem download-Attribut und einer Blob-URL als href zu erstellen.
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 Ihnen 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 abgelegt. 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.
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 Progressive Enhancement-Muster eingehen. In Browsern, die die File System Access API nicht unterstützen, lade ich die alten Skripts.
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 wird.
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 vom Typ 'save-file' an die Methode chooseFileSystemEntries() übergeben.
Daraufhin wird ein Dialogfeld zum Speichern der Datei angezeigt.
Da die Datei geöffnet ist, ist dies 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 immer 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.
Das Freigabeblatt von Safari für Computer unter macOS wird beispielsweise aufgerufen, wenn ein Nutzer auf Artikel teilen in meinem Blog klickt. Sie können einen Link zum Artikel über die macOS-Nachrichten-App mit einem Freund teilen.
Dazu rufe ich navigator.share() auf und übergebe ein optionales title, text und url in einem Objekt.
Was ist, 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 das mit der Fugu-Anwendung für Grußkarten funktioniert.
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 weitergegeben 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 Verbesserung.
Wenn sowohl 'share' als auch 'canShare' im navigator-Objekt vorhanden sind, lade ich share.mjs mit dynamischem 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 könnten mich Telefonnummern, E‑Mail-Adressen, Avatarsymbole oder physische Adressen interessieren.
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 idealen Eigenschaften für die vom Nutzer ausgewählten Kontakte zurückzugeben.
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.
Asynchronous Clipboard API
Als Nächstes geht es um das Kopieren und Einfügen. Als Softwareentwickler nutzen wir häufig die Funktion zum Kopieren und Einfügen. 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 Kopieren und Einfügen in die Fugu Greetings App eingebaut habe.
Damit ich etwas in die Zwischenablage des Systems kopieren kann, muss ich darauf schreiben.
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, 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 Möglichkeit, die API zu demonstrieren, ist die Verwendung in Fugu Greetings als Strichzähler.
Ich habe einen Event-Listener hinzugefügt, der den Zähler für Stifteingaben erhöht, wenn das pointerdown-Ereignis eintritt, und dann das aktualisierte Symbolbadge festlegt.
Wenn der Arbeitsbereich 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.
Im ersten Schritt registrieren Sie ein Ereignis für die regelmäßige Synchronisierung in der Registrierung des Service Workers. Es wird auf ein Synchronisierungstag namens 'image-of-the-day' gewartet. Das Mindestintervall beträgt einen Tag, sodass der Nutzer alle 24 Stunden ein neues Hintergrundbild erhalten kann.
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 mit der 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 eines dynamischen import() (das in einem Service Worker-Kontext noch nicht unterstützt wird) das 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 mit der API für die regelmäßige Hintergrundsynchronisierung aktualisiert wird.
Notification Triggers API
Manchmal braucht man selbst bei viel Inspiration einen Anstoß, um eine begonnene Grußkarte fertigzustellen. 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 Erweiterung. 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 ich bereits geschrieben habe, 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 wird mit navigator.wakelock.request method() ein Wake Lock abgerufen.
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 Ihre 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. Diese API ist durch die Berechtigung für Benachrichtigungen geschützt, 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 aktiv verwendet.
Nachdem ich sichergestellt habe, dass die Berechtigung für Benachrichtigungen erteilt wurde, instanziere ich den Inaktivitätserkennung. 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 wird nur der relevante Code geladen. 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 kann in den einzelnen Browsern etwas anders aussehen, da nicht alle Plattformen alle Funktionen unterstützen. Die Kernfunktionen sind jedoch immer vorhanden und werden entsprechend den Möglichkeiten des jeweiligen Browsers nach und nach erweitert. Diese Funktionen können sich sogar in ein und demselben Browser ändern, je nachdem, ob die App als installierte App oder in einem Browser-Tab ausgeführt wird.
Sie können Fugu auf GitHub forken.
Das Chromium-Team arbeitet mit Hochdruck daran, die Situation bei erweiterten Fugu-APIs zu verbessern. Durch die Anwendung von Progressive Enhancement beim Erstellen 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.
Dieses Dokument 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.