Für moderne Browser entwickeln und kontinuierlich verbessern, als wäre es 2003
Im März 2003 verblüfften Nick Finck und Steve Champeon die Webdesign-Welt mit dem Konzept der progressiven Verbesserung, einer Strategie für das Webdesign, bei der zuerst die Hauptinhalte der Webseite geladen werden und dann nach und nach nuanciertere und technisch anspruchsvollere Präsentationsebenen und Funktionen hinzugefügt werden. 2003 ging es bei der progressiven Verbesserung darum, die damals modernen CSS-Funktionen, unaufdringliches JavaScript und sogar nur skalierbare Vektorgrafiken zu verwenden. Bei der progressiven Verbesserung im Jahr 2020 und darüber hinaus geht es darum, moderne Browserfunktionen zu nutzen.
Modernes JavaScript
Apropos JavaScript: Die Browserunterstützung für die neuesten JavaScript-Kernfunktionen von ES 2015 ist hervorragend.
Der neue Standard umfasst unter anderem Promises, Module, Klassen, Vorlagenliterale, Pfeilfunktionen, let
und const
, Standardparameter, Generatoren, die Destrukturierungszuweisung, Rest und Spread, Map
/Set
, WeakMap
/WeakSet
und vieles mehr.
Alle werden unterstützt.
Asynchrone Funktionen sind eine Funktion von ES 2017 und eine meiner persönlichen Favoriten. Sie können in allen gängigen Browsern verwendet werden.
Mit den Keywords async
und await
kann asynchrones, versprechensbasiertes Verhalten in einem klareren Stil geschrieben werden, sodass Promise-Ketten nicht explizit konfiguriert werden müssen.
Und selbst die jüngsten Spracherweiterungen in ES 2020 wie optionale Verknüpfung und Nullzusammenführung wurden sehr schnell unterstützt. Unten sehen Sie ein Codebeispiel. Was die wichtigsten JavaScript-Funktionen angeht, ist das Gras heute so grün wie nie zuvor.
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 🐡, ein Projekt, das dem Web alle Funktionen von Android-/iOS-/Desktop-Anwendungen verleihen soll. Weitere Informationen zum Projekt finden Sie auf der Landingpage.
Fugu Greetings ist eine Zeichen-App, mit der Sie virtuelle Grußkarten erstellen und an Ihre Lieben senden können. Sie veranschaulicht die Grundkonzepte von PWAs. Sie ist zuverlässig und vollständig offlinefähig. Sie können sie also auch ohne Netzwerk nutzen. Sie kann auch auf dem Startbildschirm eines Geräts installiert werden und lässt sich als eigenständige Anwendung nahtlos in das Betriebssystem einbinden.
Progressive Verbesserung
Jetzt ist es an der Zeit, über progressive Verbesserung zu sprechen. Im MDN Web Docs-Glossar wird das Konzept so definiert:
Progressive Verbesserung ist eine Designphilosophie, die so vielen Nutzern wie möglich grundlegende Inhalte und Funktionen bietet und gleichzeitig die bestmögliche Nutzererfahrung nur für Nutzer der modernsten Browser ermöglicht, die den gesamten erforderlichen Code ausführen können.
Die Funktionserkennung wird in der Regel verwendet, um festzustellen, ob Browser modernere Funktionen verarbeiten können. Polyfills werden hingegen häufig verwendet, um fehlende Funktionen mit JavaScript hinzuzufügen.
[…]
Progressive Verbesserung ist eine nützliche Methode, mit der sich Webentwickler darauf konzentrieren können, die bestmöglichen Websites zu entwickeln und gleichzeitig dafür zu sorgen, dass diese Websites auf mehreren unbekannten User-Agents funktionieren. Graceful Degradation ist ähnlich, aber nicht dasselbe. Es wird oft als gegenteilige Strategie zur progressiven Verbesserung angesehen. In Wirklichkeit sind beide Ansätze gültig und können sich oft gegenseitig ergänzen.
MDN-Mitwirkende
Jede Grußkarte von Grund auf neu zu erstellen, kann sehr mühsam sein.
Warum also nicht eine Funktion, mit der Nutzer ein Bild importieren und von dort aus beginnen können?
Bei einem traditionellen Ansatz hätten Sie dazu ein <input type=file>
-Element verwendet.
Zuerst erstellen Sie das Element, legen seine type
auf 'file'
fest und fügen der accept
-Eigenschaft MIME-Typen hinzu. Anschließend klicken Sie programmatisch darauf und warten auf Änderungen.
Wenn Sie ein Bild auswählen, wird es direkt in den Canvas 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 traditionelle Methode zum Speichern von Dateien besteht darin, einen Ankerlink mit dem Attribut download
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 URL des Blob-Objekts 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 Moment. 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 direkt in Ihren Downloadordner verschoben. Das ist nicht gut.
Was wäre, wenn es eine bessere Möglichkeit gäbe? Was wäre, wenn Sie eine lokale Datei einfach öffnen, bearbeiten und die Änderungen entweder in einer neuen Datei oder in der ursprünglichen Datei speichern könnten, die Sie ursprünglich geöffnet hatten? Die File System Access API ermöglicht es Ihnen, Dateien und Verzeichnisse zu öffnen und zu erstellen sowie zu ändern und zu speichern.
Wie kann ich also die Funktionen einer API erkennen?
Die File System Access API stellt eine neue Methode window.chooseFileSystemEntries()
bereit.
Daher muss ich unterschiedliche Import- und Exportmodule bedingt laden, je nachdem, ob diese Methode verfügbar ist. Unten habe ich gezeigt, 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 das Muster der progressiven Verbesserung hervorheben. In Browsern, die die File System Access API derzeit nicht unterstützen, lade ich die alten Scripts. Unten sehen Sie die Netzwerktabs von Firefox und Safari.
In Chrome, einem Browser, der die API unterstützt, werden jedoch nur die neuen Scripts geladen.
Das ist dank dynamischer import()
möglich, die von allen modernen Browsern unterstützt wird.
Wie ich bereits sagte, ist das Gras derzeit ziemlich grün.
File System Access API
Nachdem ich das geklärt habe, sehen wir uns die tatsächliche Implementierung an, die auf der File System Access API basiert.
Zum Importieren eines Bildes rufe ich window.chooseFileSystemEntries()
auf und übergebe ihm das Attribut accepts
, in dem ich angeben, dass ich Bilddateien möchte.
Sowohl Dateiendungen als auch MIME-Typen werden unterstützt.
Dies führt zu einem Dateihandle, über den ich die tatsächliche 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 identisch, aber dieses Mal muss ich der Methode chooseFileSystemEntries()
einen Typparameter von 'save-file'
übergeben.
Daraufhin wird ein Dialogfeld zum Speichern der Datei angezeigt.
Wenn die Datei geöffnet ist, ist dies nicht erforderlich, da 'open-file'
der Standard ist.
Ich habe den Parameter accepts
ähnlich wie zuvor festgelegt, diesmal jedoch nur auf PNG-Bilder beschränkt.
Wieder wird ein Dateihandle zurückgegeben, aber anstatt die Datei abzurufen, erstelle ich diesmal einen beschreibbaren Stream, indem ich createWritable()
aufrufe.
Als Nächstes schreibe ich den Blob, also das Bild meiner Grußkarte, in die Datei.
Schließlich schließe ich den Schreibstream.
Es kann immer etwas schiefgehen: Auf dem Laufwerk ist möglicherweise kein Speicherplatz mehr verfügbar, es kann ein Schreib- oder Lesefehler auftreten oder der Nutzer schließt das Dateidialogfeld einfach nur wieder.
Deshalb umschließen ich die Aufrufe immer in 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);
}
};
Mithilfe der File System Access API kann ich eine Datei wie gewohnt öffnen. Die importierte Datei wird direkt auf dem Canvas gezeichnet. Ich kann meine Änderungen vornehmen und sie dann über ein echtes Speicherdialogfeld speichern, in dem ich den Namen und Speicherort der Datei auswählen kann. Jetzt ist die Datei für die Ewigkeit gesichert.
Web Share API und Web Share Target API
Außer für die Ewigkeit aufzubewahren, möchte ich meine Grußkarte vielleicht auch teilen. Das ist mit der Web Share API und der Web Share Target API möglich. Mobil- und seit Kurzem auch Desktop-Betriebssysteme haben integrierte Freigabemechanismen. Unten sehen Sie beispielsweise das Freigabe-Sheet von Safari auf dem Mac, 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-Nachrichten-App.
Der Code dafür ist ziemlich einfach. Ich rufe navigator.share()
auf und übergebe ihm 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 die Web Share Level 2-Funktionen um Dateifreigabefunktionen erweitert wurden.
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-Grußkartenanwendung funktioniert.
Zuerst muss ich ein data
-Objekt mit einem files
-Array aus einem Blob und dann eine title
und eine text
vorbereiten. Als Nächstes verwende ich als Best Practice die neue navigator.canShare()
-Methode, die genau das tut, was ihr Name vermuten lässt: Sie gibt mir 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 anzurufen.
Da alles schiefgehen 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 die progressive Verbesserung.
Wenn sowohl 'share'
als auch 'canShare'
im navigator
-Objekt vorhanden sind, lade ich share.mjs
über dynamische import()
nur dann.
In Browsern wie Safari für Mobilgeräte, 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 auf Android auf die Schaltfläche Teilen tippe, wird das integrierte Freigabe-Sheet geöffnet. Ich kann beispielsweise Gmail auswählen und das Widget für den E-Mail-Editor wird mit dem angehängten Bild angezeigt.
Contact Picker API
Als Nächstes möchte ich über Kontakte sprechen, also das Adressbuch oder die Kontaktverwaltungs-App eines Geräts. Wenn Sie eine Grußkarte schreiben, ist es manchmal nicht ganz einfach, den Namen einer Person richtig zu schreiben. Ich habe beispielsweise einen Freund namens Sergej, der seinen Namen lieber in kyrillischen Buchstaben geschrieben haben möchte. Ich verwende eine deutsche QWERTZ-Tastatur und weiß nicht, wie ich den Namen eingeben soll. Dieses Problem kann mit der Contact Picker API gelöst werden. Da ich meine Freunde in der Kontakte App meines Smartphones gespeichert habe, kann ich über die Contacts Picker API auf meine Kontakte im Web zugreifen.
Zuerst muss ich die Liste der Properties angeben, auf die ich zugreifen möchte.
In diesem Fall benötige ich nur die Namen, aber für andere Anwendungsfälle könnte ich an Telefonnummern, E-Mail-Adressen, Avatarsymbolen oder Postanschriften interessiert sein.
Als Nächstes konfiguriere ich ein options
-Objekt und setze multiple
auf true
, damit ich mehrere Einträge auswählen kann.
Schließlich kann ich navigator.contacts.select()
aufrufen, wodurch die gewünschten Eigenschaften für die vom Nutzer ausgewählten Kontakte zurückgegeben werden.
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);
}
};
Und Sie haben wahrscheinlich schon das Muster erkannt: Ich lade die Datei nur dann, 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, Сергей Михайлович Брин und 劳伦斯·爱德华·"拉里"·佩奇, auswähle, sehen Sie, dass in der Kontaktauswahl nur ihre Namen, aber nicht ihre E-Mail-Adressen oder andere Informationen wie ihre Telefonnummern angezeigt werden. Die Namen werden dann auf die Grußkarte geschrieben.
Die Asynchronous Clipboard API
Als Nächstes geht es um das Kopieren und Einfügen. Eine unserer Lieblingsaktionen als Softwareentwickler ist das Kopieren und Einfügen. Als Verfasser von Grußkarten möchte ich das manchmal auch tun. Ich möchte beispielsweise ein Bild in eine Grußkarte einfügen, an der ich gerade arbeite, oder die Grußkarte kopieren, damit ich sie an anderer Stelle weiter bearbeiten kann. Die Async Clipboard API unterstützt sowohl Text als auch Bilder. Ich erkläre Ihnen, wie ich der Fugu Greetings App die Unterstützung für Kopieren und Einfügen hinzugefügt habe.
Wenn ich etwas in die Zwischenablage des Systems kopieren möchte, 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 Elemente aus der Zwischenablage durchgehen, die ich durch Aufrufen von navigator.clipboard.read()
erhalte.
Das liegt daran, dass sich möglicherweise mehrere Zwischenablageelemente in unterschiedlichen Darstellungen in der Zwischenablage befinden.
Jedes Zwischenablageelement hat ein types
-Feld, das mir die MIME-Typen der verfügbaren Ressourcen angibt.
Ich rufe die getType()
-Methode des Zwischenablage-Elements auf und übergebe den zuvor abgerufenen MIME-Typ.
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 ist mittlerweile fast selbstverständlich. 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 Vorschau-App von macOS geöffnet und kopiere es in die Zwischenablage. Wenn ich auf Einfügen klicke, werde ich von der Fugu Greetings App gefragt, ob ich der App erlauben möchte, Text und Bilder in der Zwischenablage zu sehen.
Nachdem Sie die Berechtigung akzeptiert haben, wird das Bild in die Anwendung eingefügt. Das funktioniert auch andersherum. Ich 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 Zähler für Stiftstriche zu verwenden.
Ich habe einen Event-Listener hinzugefügt, der den Zähler für die Stiftstriche erhöht, wenn das Ereignis pointerdown
auftritt, und dann das aktualisierte Symbol-Badge festlegt.
Wenn der Canvas gelöscht wird, wird der Zähler zurückgesetzt und das Symbol entfernt.
let strokes = 0;
canvas.addEventListener('pointerdown', () => {
navigator.setAppBadge(++strokes);
});
clearButton.addEventListener('click', () => {
strokes = 0;
navigator.setAppBadge(strokes);
});
Diese Funktion ist eine progressive Verbesserung. Die Ladelogik funktioniert also wie gewohnt.
if ('setAppBadge' in navigator) {
import('./badge.mjs');
}
In diesem Beispiel habe ich die Zahlen 1 bis 7 mit jeweils einem Strich gezeichnet. Die Anzahl der Badges auf dem Symbol ist jetzt auf sieben gestiegen.
Periodic Background Sync API
Möchtest du jeden Tag mit etwas Neuem beginnen? Eine praktische Funktion der Fugu Greetings App ist, dass Sie jeden Morgen mit einem neuen Hintergrundbild für Ihre Grußkarte inspiriert werden können. Dazu verwendet die App die Periodic Background Sync API.
Im ersten Schritt müssen Sie ein regelmäßiges Synchronisierungsereignis in der Service Worker-Registrierung registrieren.
Es wartet auf ein Synchronisierungs-Tag namens 'image-of-the-day'
und hat ein Mindestintervall von einem Tag, damit 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 müssen Sie im Service Worker auf das Ereignis periodicsync
lauschen.
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 Canvases 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 dies ist eine echte progressive Verbesserung. Der Code wird also nur geladen, wenn die API vom Browser unterstützt wird.
Das gilt sowohl für den Clientcode als auch für den Service Worker-Code.
In nicht unterstützten Browsern wird keines der beiden geladen.
Beachten Sie, dass ich im Service Worker anstelle einer dynamischen import()
(die noch nicht in einem Service Worker-Kontext unterstützt wird) die klassische importScripts()
verwende.
// 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');
}
In Fugu Greetings wird durch Drücken der Schaltfläche Hintergrund das Grußkartenbild des Tages angezeigt, das jeden Tag über die Periodic Background Sync API aktualisiert wird.
Notification Triggers API
Manchmal braucht man auch bei viel Inspiration einen kleinen Anstoß, um eine angefangene Grußkarte fertigzustellen. Diese Funktion wird durch die Notification Triggers API aktiviert. Als Nutzer kann ich eine Uhrzeit eingeben, zu der ich daran erinnert werden möchte, meine Grußkarte fertigzustellen. Dann erhalte ich eine Benachrichtigung, dass meine Grußkarte bereit ist.
Nachdem Sie nach der gewünschten Uhrzeit gefragt wurden, plant die Anwendung die Benachrichtigung mit einer showTrigger
.
Das kann ein TimestampTrigger
mit dem zuvor ausgewählten Zieldatum sein.
Die Erinnerung wird lokal ausgelöst, eine Netzwerk- oder Serverseite ist nicht 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 allen anderen Dingen, die ich bisher gezeigt habe, handelt es sich hierbei 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 anklicke, 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 keine Netzwerkverbindung erforderlich.
Die Wake Lock API
Ich möchte auch die Wake Lock API einbinden. Manchmal muss man einfach nur lange genug auf den Bildschirm starren, bis die Inspiration zuschlägt. Im schlimmsten Fall schaltet sich das Display aus. Mit der Wake Lock API lässt sich das verhindern.
Im ersten Schritt müssen Sie mit der navigator.wakelock.request method()
eine Wake-Lock erhalten.
Ich übergebe den String 'screen'
, um eine Sperre für das Aufwecken des Displays zu erhalten.
Dann füge ich einen Ereignis-Listener hinzu, um benachrichtigt zu werden, wenn die Sperre aufgehoben wird.
Das kann beispielsweise passieren, wenn sich die Sichtbarkeit des Tabs ändert.
In diesem Fall kann ich die Wake-Lock wiedererlangen, 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 Verbesserung. 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 ein Kästchen für Schlaflosigkeit. Wenn Sie es anklicken, bleibt der Bildschirm eingeschaltet.
Die Idle Detection API
Manchmal ist es einfach nutzlos, auch wenn Sie stundenlang auf den Bildschirm starren, und Sie können sich nicht im Geringsten vorstellen, was Sie mit Ihrer Grußkarte machen sollen. Mit der Idle Detection API kann die App die Inaktivität von Nutzern erkennen. Wenn der Nutzer zu lange inaktiv ist, wird die App auf den ursprünglichen Zustand zurückgesetzt und der Canvas gelöscht. Diese API ist derzeit an die Berechtigung „Benachrichtigungen“ gebunden, 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 Benachrichtigungsberechtigung erteilt wurde, erstelle ich den Inaktivitätsdetektor. Ich registriere einen Ereignis-Listener, der auf Inaktivitätsänderungen wartet, einschließlich des Nutzers und des Bildschirmstatus. Der Nutzer kann aktiv oder inaktiv sein und der Bildschirm kann entsperrt oder gesperrt sein. Wenn der Nutzer inaktiv ist, wird der Canvas gelöscht. Ich lege für den Inaktivitätssensor einen Grenzwert von 60 Sekunden fest.
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 Ephemerid 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 Nutzer nie die Downloadkosten für eine Funktion bezahlen, die ihr Browser nicht unterstützt. Mithilfe der progressiven Verbesserung kann ich dafür sorgen, dass nur der relevante Code geladen wird. Da Anfragen mit HTTP/2 kostengünstig sind, sollte dieses Muster für viele Anwendungen gut funktionieren. Für sehr große Apps sollten Sie jedoch einen Bundler in Betracht ziehen.
Die App kann in jedem Browser etwas anders aussehen, da nicht alle Plattformen alle Funktionen unterstützen. Die Hauptfunktionen sind jedoch immer verfügbar und werden entsprechend den Funktionen des jeweiligen Browsers kontinuierlich erweitert. Diese Funktionen können sich sogar in einem und demselben Browser ändern, je nachdem, ob die App als installierte App oder in einem Browsertab ausgeführt wird.
Wenn Sie an der Fugu Greetings-App interessiert sind, forken Sie sie auf GitHub.
Das Chromium-Team arbeitet intensiv daran, die erweiterten Fugu APIs zu verbessern. Durch die Anwendung von progressiver Verbesserung bei der Entwicklung meiner App kann ich dafür sorgen, dass alle Nutzer eine gute, solide Basiserfahrung haben, aber Nutzer, die Browser verwenden, die mehr Webplattform-APIs unterstützen, eine noch bessere Erfahrung haben. Ich bin gespannt, wie Sie die progressive Verbesserung 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 zu klären.