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 differenziertere 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 Promis, Module, Klassen, Vorlagenliterale, Pfeilfunktionen, let
und const
, Standardparameter, Generatoren, die destruktive Zuweisung, Ruhe und Verteilung, Map
/Set
, WeakMap
/WeakSet
und viele 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 übersichtlicher 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 wichtige Voraussetzung für Project Fugu 🐡, das dem Web alle Funktionen von Android-, iOS- und Desktop-Apps zur Verfügung stellen 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 Kernkonzepte 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 Enhancement
Jetzt ist es an der Zeit, über progressive Verbesserung zu sprechen. Im Glossar der MDN-Webdokumente wird das Konzept so definiert:
Progressive Verbesserung ist eine Designphilosophie, die so vielen Nutzern wie möglich grundlegende 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.
Die Funktionserkennung wird in der Regel verwendet, um festzustellen, ob Browser modernere Funktionen verarbeiten können, während Polyfills häufig verwendet werden, 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-Beitragende
Jede Grußkarte von Grund auf neu zu erstellen, kann sehr mühsam sein.
Wie wäre es also mit einer Funktion, mit der Nutzer Bilder importieren und direkt loslegen 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 können auch programmatisch darauf klicken, um den Download auszulösen. Um Speicherlecks zu vermeiden, sollten Sie unbedingt die Blob-Objekt-URL widerrufen.
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 Lösung 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? Mit der File System Access API können Sie Dateien und Verzeichnisse öffnen und erstellen sowie ändern und speichern.
Wie kann ich also die Funktionen einer API erkennen?
Die File System Access API stellt die neue Methode window.chooseFileSystemEntries()
zur Verfügung.
Daher muss ich abhängig davon, ob diese Methode verfügbar ist, bedingt verschiedene Import- und Exportmodule laden. 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);
}
};
Der Export eines Bildes ist fast identisch, aber dieses Mal muss ich den Typparameter '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'
der Standard ist.
Ich habe den accepts
-Parameter ähnlich wie zuvor festgelegt, aber diesmal nur auf PNG-Bilder.
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 beschreibbaren Stream.
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.
Aus diesem Grund 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 übergeben ihm ein optionales title
, text
und url
in einem Objekt.
Aber 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 Web Share Level 2 Funktionen zur Dateifreigabe hinzugefügt hat.
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 freigegeben werden können, kann ich wie zuvor navigator.share()
aufrufen.
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 zum Beispiel einen Freund Sergey, der seinen Namen lieber in kyrillischen Buchstaben schreiben möchte. Ich verwende eine deutsche QWERTZ-Tastatur und habe keine Ahnung, wie ich den Namen eintippen soll. Dieses Problem kann mit der Contact Picker API gelöst werden. Da ich meinen Freund 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 Unterkünfte 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. Ihre Namen werden dann auf meine Grußkarte gezeichnet.
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 Element in der Zwischenablage ist im Wesentlichen ein Objekt mit einem Blob als Wert und dem Blob-Typ 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 Element in der Zwischenablage hat ein Feld types
, in dem die MIME-Typen der verfügbaren Ressourcen angegeben sind.
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. Andersherum funktioniert es auch. 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 Begrüßung 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. Der Badge-Zähler auf dem Symbol steht jetzt bei 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 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);
}
};
Der zweite Schritt besteht darin, im Service Worker auf das Ereignis periodicsync
zu warten.
Wenn das Ereignis-Tag 'image-of-the-day'
ist, also das Ereignis-Tag, das zuvor registriert wurde, wird das Bild des Tages über die Funktion getImageOfTheDay()
abgerufen und das Ergebnis an alle Clients weitergegeben, damit diese 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.
Im Service Worker verwende ich anstelle eines dynamischen import()
(das in einem Service Worker-Kontext noch nicht unterstützt wird) das klassische importScripts()
.
// 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 aufgefordert werden soll, 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 Erinnerungsbenachrichtigung wird lokal ausgelöst und erfordert weder ein Netzwerk noch eine Serverseite.
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 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 müssen Sie nur lange genug auf den Bildschirm starren, bis Sie die Inspiration küsst. Das Schlimmste, was dann passieren kann, ist, dass sich der Bildschirm abschaltet. Die Wake Lock API kann dies 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 Event-Listener hinzu, der informiert wird, wenn der Wakelock freigegeben wird.
Das kann beispielsweise passieren, wenn sich die Sichtbarkeit des Tabs ändert.
In diesem Fall kann ich, wenn der Tab wieder sichtbar wird, die Wake-Lock wiedererlangen.
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 Insomnia (Schlaflosigkeit). Wenn dieses Kästchen angeklickt ist, bleibt der Bildschirm aktiv.
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 nur eine Benachrichtigung an ein Gerät zu senden, das der Nutzer gerade aktiv nutzt.
Nachdem ich sichergestellt habe, dass die Benachrichtigungsberechtigung erteilt wurde, erstelle ich den Inaktivitätsdetektor. Ich registriere einen Event-Listener, der Änderungen bei Inaktivität beobachtet, einschließlich Nutzer- und Bildschirmstatus. Der Nutzer kann aktiv oder inaktiv sein und der Bildschirm kann entsperrt oder gesperrt werden. Wenn der Nutzer inaktiv ist, wird der Canvas gelöscht. Ich lege für den Inaktivitätsmelder 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 ein Taxi. So viele APIs in nur einer Beispiel-App. Und denken Sie daran, dass ich nie dafür sage, dass Nutzer die Downloadkosten für eine Funktion zahlen müssen, 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 auch in ein und demselben Browser ändern, je nachdem, ob die App als installierte Anwendung 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, das Gras für moderne Fugu-APIs umweltfreundlicher zu gestalten. 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 an Fugu-Grüßen mitgewirkt 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.