Développer pour les navigateurs modernes et améliorer progressivement comme en 2003
Date de publication : 29 juin 2020
En mars 2003, Nick Finck et Steve Champeon ont stupéfié le monde du Web design avec le concept d'amélioration progressive. Il s'agit d'une stratégie de Web design qui consiste à charger d'abord le contenu principal d'une page Web, puis à ajouter progressivement des couches de présentation et des fonctionnalités plus nuancées et techniquement rigoureuses au-dessus du contenu. En 2003, l'amélioration progressive consistait à utiliser des fonctionnalités CSS modernes, du JavaScript non intrusif et même des graphiques vectoriels adaptables. En 2020 et au-delà, l'amélioration progressive consiste à utiliser les fonctionnalités modernes des navigateurs.
JavaScript moderne
En parlant de JavaScript, la situation de la compatibilité des navigateurs avec les dernières fonctionnalités JavaScript ES 2015 de base est excellente. La nouvelle norme inclut les promesses, les modules, les classes, les littéraux de modèle, les fonctions fléchées, let et const, les paramètres par défaut, les générateurs, l'affectation par déstructuration, le reste et la propagation, Map/Set, WeakMap/WeakSet, et bien d'autres.
Tous les types de fichiers sont acceptés.
Les fonctions asynchrones, une fonctionnalité ES 2017 qui est l'une de mes préférées, peuvent être utilisées dans tous les principaux navigateurs.
Les mots clés async et await permettent d'écrire un comportement asynchrone basé sur des promesses dans un style plus clair, en évitant d'avoir à configurer explicitement des chaînes de promesses.
Même les ajouts de langage ES 2020 les plus récents, comme le chaînage optionnel et la coalescence des valeurs nulles, ont été pris en charge très rapidement. En ce qui concerne les fonctionnalités JavaScript de base, il est difficile de faire mieux.
Exemple :
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah',
},
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
Application exemple : Fugu Greetings
Pour ce document, je travaille avec une PWA appelée Fugu Greetings (GitHub). Le nom de cette application est un clin d'œil au projet Fugu 🐡, qui vise à donner au Web toutes les capacités des applications Android, iOS et de bureau. Pour en savoir plus sur le projet, consultez sa page de destination.
Fugu Greetings est une application de dessin qui vous permet de créer des cartes de vœux virtuelles et de les envoyer à vos proches. Il illustre les concepts fondamentaux des PWA. Il est fiable et entièrement utilisable hors connexion. Vous pouvez donc l'utiliser même si vous n'avez pas de réseau. Elle est également installable sur l'écran d'accueil d'un appareil et s'intègre parfaitement au système d'exploitation en tant qu'application autonome.
Amélioration progressive
Maintenant que nous avons abordé ce point, parlons de l'amélioration progressive. Le glossaire MDN Web Docs définit ce concept comme suit :
L'amélioration progressive est une philosophie de conception qui fournit une base de contenu et de fonctionnalités essentielles au plus grand nombre d'utilisateurs possible, tout en offrant la meilleure expérience possible uniquement aux utilisateurs des navigateurs les plus modernes capables d'exécuter tout le code requis.
La détection de fonctionnalités est généralement utilisée pour déterminer si les navigateurs peuvent gérer des fonctionnalités plus modernes, tandis que les polyfills sont souvent utilisés pour ajouter des fonctionnalités manquantes avec JavaScript.
[…]
L'amélioration progressive est une technique utile qui permet aux développeurs Web de se concentrer sur la création des meilleurs sites Web possible tout en s'assurant qu'ils fonctionnent sur plusieurs user-agents inconnus. La dégradation élégante est liée à l'amélioration progressive, mais n'est pas la même chose et est souvent considérée comme allant dans la direction opposée. En réalité, les deux approches sont valides et peuvent souvent se compléter.
Contributeurs MDN
Il peut être très fastidieux de créer chaque carte de vœux de A à Z.
Alors pourquoi ne pas proposer une fonctionnalité qui permet aux utilisateurs d'importer une image et de commencer à partir de là ?
Avec une approche traditionnelle, vous auriez utilisé un élément <input type=file> pour ce faire.
Vous devez d'abord créer l'élément, définir son type sur 'file', ajouter des types MIME à la propriété accept, puis le "cliquer" de manière programmatique et écouter les modifications. Lorsque vous sélectionnez une image, elle est importée directement sur le canevas.
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();
});
};
Lorsqu'une fonctionnalité d'importation est disponible, il est probable qu'une fonctionnalité d'exportation soit également nécessaire pour permettre aux utilisateurs d'enregistrer leurs cartes de vœux localement.
La méthode traditionnelle pour enregistrer des fichiers consiste à créer un lien d'ancrage avec un attribut download et une URL blob comme href.
Vous le "cliquerez" également de manière programmatique pour déclencher le téléchargement et, pour éviter les fuites de mémoire, vous n'oublierez pas, espérons-le, de révoquer l'URL de l'objet blob.
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();
};
Mais attendez une minute. Mentalement, vous n'avez pas "téléchargé" une carte de vœux, mais vous l'avez "enregistrée". Au lieu d'afficher une boîte de dialogue "Enregistrer" qui vous permet de choisir où placer le fichier, le navigateur a directement téléchargé la carte de vœux sans interaction de l'utilisateur et l'a placée directement dans votre dossier Téléchargements. Ce n'est pas idéal.
Et s'il existait une meilleure façon de procéder ? Et si vous pouviez simplement ouvrir un fichier local, le modifier, puis enregistrer les modifications dans un nouveau fichier ou dans le fichier d'origine que vous aviez ouvert au départ ? Il s'avère que oui. L'API File System Access vous permet d'ouvrir, de créer, de modifier et d'enregistrer des fichiers et des répertoires .
Comment détecter une API ?
L'API File System Access expose une nouvelle méthode window.chooseFileSystemEntries().
Par conséquent, je dois charger conditionnellement différents modules d'importation et d'exportation selon que cette méthode est disponible ou non.
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'),
]);
}
};
Mais avant de me plonger dans les détails de l'API File System Access, permettez-moi de souligner rapidement le modèle d'amélioration progressive ici. Sur les navigateurs qui ne sont pas compatibles avec l'API File System Access, je charge les anciens scripts.
Toutefois, sur Chrome, un navigateur compatible avec l'API, seuls les nouveaux scripts sont chargés.
Cela est possible de manière élégante grâce aux import() dynamiques, que tous les navigateurs modernes prennent en charge.
Comme je l'ai dit plus tôt, l'herbe est plutôt verte ces derniers temps.
API File System Access
Maintenant que j'ai abordé ce point, il est temps d'examiner l'implémentation réelle basée sur l'API File System Access.
Pour importer une image, j'appelle window.chooseFileSystemEntries() et lui transmets une propriété accepts dans laquelle j'indique que je souhaite des fichiers image.
Les extensions de fichier et les types MIME sont acceptés.
Cela génère un descripteur de fichier à partir duquel je peux obtenir le fichier réel en appelant getFile().
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);
}
};
L'exportation d'une image est presque identique, mais cette fois, je dois transmettre un paramètre de type 'save-file' à la méthode chooseFileSystemEntries().
Une boîte de dialogue d'enregistrement de fichier s'affiche.
Lorsque le fichier est ouvert, cela n'est pas nécessaire, car 'open-file' est la valeur par défaut.
J'ai défini le paramètre accepts de la même manière que précédemment, mais cette fois limité aux images PNG.
Là encore, j'obtiens un descripteur de fichier, mais au lieu d'obtenir le fichier, je crée cette fois un flux accessible en écriture en appelant createWritable().
Ensuite, j'écris le blob, qui est l'image de ma carte de vœux, dans le fichier.
Enfin, je ferme le flux accessible en écriture.
Tout peut toujours échouer : le disque peut manquer d'espace, une erreur d'écriture ou de lecture peut se produire, ou l'utilisateur peut simplement annuler la boîte de dialogue de fichier.
C'est pourquoi j'encapsule toujours les appels dans une instruction try...catch.
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);
}
};
En utilisant l'amélioration progressive avec l'API File System Access, je peux ouvrir un fichier comme avant. Le fichier importé est dessiné directement sur le canevas. Je peux effectuer mes modifications et les enregistrer avec une boîte de dialogue d'enregistrement réelle, dans laquelle je peux choisir le nom et l'emplacement de stockage du fichier. Le fichier est désormais prêt à être conservé pour l'éternité.
API Web Share et Web Share Target

En plus de la stocker pour l'éternité, je veux peut-être partager ma carte de vœux. C'est ce que me permettent de faire les API Web Share et Web Share Target. Les systèmes d'exploitation mobiles et, plus récemment, de bureau ont intégré des mécanismes de partage.
Par exemple, la feuille de partage de Safari sur macOS est déclenchée lorsqu'un utilisateur clique sur Partager l'article sur mon blog. Vous pouvez partager un lien vers l'article avec un ami à l'aide de l'application Messages de macOS.
Pour ce faire, j'appelle navigator.share() et lui transmets un title, text et url facultatifs dans un objet.
Mais que faire si je veux joindre une image ? Le niveau 1 de l'API Web Share ne le permet pas encore.
La bonne nouvelle, c'est que le partage Web de niveau 2 a ajouté des fonctionnalités de partage de fichiers.
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);
}
Je vais vous montrer comment faire fonctionner cela avec l'application de carte de vœux Fugu.
Tout d'abord, je dois préparer un objet data avec un tableau files composé d'un blob, puis un title et un text. Ensuite, en suivant les bonnes pratiques, j'utilise la nouvelle méthode navigator.canShare(), qui fait ce que son nom suggère : elle m'indique si l'objet data que j'essaie de partager peut techniquement être partagé par le navigateur.
Si navigator.canShare() m'indique que les données peuvent être partagées, je suis prêt à appeler navigator.share() comme avant.
Comme tout peut échouer, j'utilise à nouveau un bloc try...catch.
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);
}
};
Comme précédemment, j'utilise l'amélioration progressive.
Si 'share' et 'canShare' existent sur l'objet navigator, je continue et charge share.mjs à l'aide de import() dynamique.
Sur les navigateurs tels que Safari mobile qui ne remplissent qu'une des deux conditions, je ne charge pas la fonctionnalité.
const loadShare = () => {
if ('share' in navigator && 'canShare' in navigator) {
import('./share.mjs');
}
};
Dans Fugu Greetings, si j'appuie sur le bouton Partager dans un navigateur compatible comme Chrome sur Android, la feuille de partage intégrée s'ouvre. Par exemple, je peux choisir Gmail, et le widget de rédaction d'e-mails s'affiche avec l'image en pièce jointe.
API Contact Picker
Ensuite, je vais vous parler des contacts, c'est-à-dire du carnet d'adresses ou de l'application de gestion des contacts d'un appareil. Lorsque vous écrivez une carte de vœux, il n'est pas toujours facile d'écrire correctement le nom d'une personne. Par exemple, un de mes amis, Sergey, préfère que son nom soit écrit en lettres cyrilliques. J'utilise un clavier QWERTZ allemand et je ne sais pas comment taper son nom. L'API Contact Picker peut résoudre ce problème. Comme mon ami est enregistré dans l'application Contacts de mon téléphone, je peux accéder à mes contacts depuis le Web à l'aide de l'API Contacts Picker.
Tout d'abord, je dois spécifier la liste des propriétés auxquelles je souhaite accéder.
Dans ce cas, je ne veux que les noms, mais pour d'autres cas d'utilisation, je pourrais être intéressé par les numéros de téléphone, les adresses e-mail, les icônes d'avatar ou les adresses physiques.
Ensuite, je configure un objet options et définis multiple sur true pour pouvoir sélectionner plusieurs entrées.
Enfin, je peux appeler navigator.contacts.select(), qui renvoie les propriétés idéales pour les contacts sélectionnés par l'utilisateur.
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);
}
};
Vous avez probablement déjà compris le principe : je ne charge le fichier que lorsque l'API est réellement compatible.
if ('contacts' in navigator) {
import('./contacts.mjs');
}
Dans Fugu Greeting, lorsque j'appuie sur le bouton Contacts et que je sélectionne mes deux meilleurs amis, Сергей Михайлович Брин et 劳伦斯·爱德华·"拉里"·佩奇, vous pouvez voir que le sélecteur de contacts est limité à l'affichage de leurs noms, mais pas de leurs adresses e-mail ni d'autres informations comme leurs numéros de téléphone. Leurs noms sont ensuite dessinés sur ma carte de vœux.
API Asynchronous Clipboard
Ensuite, nous allons aborder le copier-coller. En tant que développeurs de logiciels, l'une de nos opérations préférées est le copier-coller. En tant qu'auteur de cartes de vœux, je peux parfois vouloir faire de même. Je souhaite peut-être coller une image dans une carte de vœux sur laquelle je travaille, ou copier ma carte de vœux pour pouvoir continuer à la modifier ailleurs. L'API Async Clipboard est compatible avec le texte et les images. Je vais vous expliquer comment j'ai ajouté la fonctionnalité copier-coller à l'application Fugu Greetings.
Pour copier un élément dans le presse-papiers du système, je dois y écrire.
La méthode navigator.clipboard.write() utilise un tableau d'éléments du presse-papiers comme paramètre.
Chaque élément du presse-papiers est essentiellement un objet avec un blob comme valeur et le type du blob comme clé.
const copy = async (blob) => {
try {
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
}),
]);
} catch (err) {
console.error(err.name, err.message);
}
};
Pour coller, je dois parcourir les éléments du presse-papiers que j'obtiens en appelant navigator.clipboard.read().
En effet, plusieurs éléments du presse-papiers peuvent y figurer sous différentes représentations.
Chaque élément du presse-papiers comporte un champ types qui m'indique les types MIME des ressources disponibles.
J'appelle la méthode getType() de l'élément du presse-papiers en transmettant le type MIME que j'ai obtenu précédemment.
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);
}
};
Et il est presque inutile de le préciser. Je ne le fais que sur les navigateurs compatibles.
if ('clipboard' in navigator && 'write' in navigator.clipboard) {
import('./clipboard.mjs');
}
Comment cela fonctionne-t-il concrètement ? J'ai ouvert une image dans l'application Aperçu de macOS et je l'ai copiée dans le presse-papiers. Lorsque je clique sur Coller, l'application Fugu Greetings me demande si je souhaite autoriser l'application à voir le texte et les images du presse-papiers.
Enfin, une fois l'autorisation acceptée, l'image est collée dans l'application. L'inverse fonctionne également. Copie une carte de vœux dans le presse-papiers. Lorsque j'ouvre ensuite Aperçu et que je clique sur Fichier, puis sur Nouveau à partir du presse-papiers, la carte de vœux est collée dans une nouvelle image sans titre.
API Badging
L'API Badging est une autre API utile.
En tant que PWA installable, Fugu Greetings dispose bien sûr d'une icône d'application que les utilisateurs peuvent placer dans le dock d'applications ou sur l'écran d'accueil.
Une façon amusante de présenter l'API est de l'utiliser dans Fugu Greetings, comme compteur de traits de stylo.
J'ai ajouté un écouteur d'événements qui incrémente le compteur de traits de stylet chaque fois que l'événement pointerdown se produit, puis définit le badge d'icône mis à jour.
Chaque fois que le canevas est effacé, le compteur est réinitialisé et le badge est supprimé.
let strokes = 0;
canvas.addEventListener('pointerdown', () => {
navigator.setAppBadge(++strokes);
});
clearButton.addEventListener('click', () => {
strokes = 0;
navigator.setAppBadge(strokes);
});
Cette fonctionnalité est une amélioration progressive. La logique de chargement est donc la même que d'habitude.
if ('setAppBadge' in navigator) {
import('./badge.mjs');
}
Dans cet exemple, j'ai dessiné les chiffres de un à sept en un seul coup de stylo par chiffre. Le compteur de badge sur l'icône est maintenant à sept.
API Periodic Background Sync
Vous souhaitez commencer chaque journée avec quelque chose de nouveau ? Une fonctionnalité intéressante de l'application Fugu Greetings est qu'elle peut vous inspirer chaque matin avec une nouvelle image d'arrière-plan pour commencer votre carte de vœux. Pour ce faire, l'application utilise l'API Periodic Background Sync.
La première étape consiste à enregistrer un événement de synchronisation périodique dans l'enregistrement du service worker. Il écoute un tag de synchronisation appelé 'image-of-the-day' et a un intervalle minimum d'un jour, de sorte que l'utilisateur peut obtenir une nouvelle image de fond toutes les 24 heures.
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);
}
};
La deuxième étape consiste à écouter l'événement periodicsync dans le service worker.
Si le tag d'événement est 'image-of-the-day', c'est-à-dire celui qui a été enregistré auparavant, l'image du jour est récupérée avec la fonction getImageOfTheDay(), et le résultat est propagé à tous les clients, afin qu'ils puissent mettre à jour leurs canevas et leurs caches.
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,
});
});
})()
);
}
});
Il s'agit d'une véritable amélioration progressive. Le code n'est donc chargé que lorsque l'API est compatible avec le navigateur.
Cela s'applique à la fois au code client et au code du service worker.
Sur les navigateurs non compatibles, aucun des deux n'est chargé.
Notez que dans le service worker, au lieu d'un import() dynamique (qui n'est pas encore pris en charge dans un contexte de service worker), j'utilise le classique 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');
}
Dans Fugu Greetings, appuyer sur le bouton Wallpaper révèle l'image de la carte de vœux du jour, qui est mise à jour quotidiennement avec l'API Periodic Background Sync.
API Notification Triggers
Parfois, même avec beaucoup d'inspiration, vous avez besoin d'un petit coup de pouce pour terminer une carte de vœux que vous avez commencée. Il s'agit d'une fonctionnalité activée par l'API Notification Triggers. En tant qu'utilisateur, je peux saisir une heure à laquelle je souhaite recevoir un rappel pour terminer ma carte de vœux. Le moment venu, je recevrai une notification m'indiquant que ma carte de vœux est disponible.
Après avoir demandé l'heure cible, l'application planifie la notification avec un showTrigger.
Il peut s'agir d'un TimestampTrigger avec la date cible précédemment sélectionnée.
La notification de rappel sera déclenchée localement. Aucun réseau ni serveur n'est nécessaire.
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),
});
}
Comme pour tout ce que je vous ai montré jusqu'à présent, il s'agit d'une amélioration progressive. Le code n'est donc chargé que de manière conditionnelle.
if ('Notification' in window && 'showTrigger' in Notification.prototype) {
import('./notification_triggers.mjs');
}
Lorsque je coche la case Rappel dans Fugu Greetings, une invite me demande quand je souhaite être rappelé pour terminer ma carte de vœux.
Lorsqu'une notification planifiée se déclenche dans Fugu Greetings, elle s'affiche comme n'importe quelle autre notification, mais comme je l'ai écrit précédemment, elle ne nécessitait pas de connexion réseau.
API Wake Lock
Je souhaite également inclure l'API Wake Lock. Il suffit parfois de fixer l'écran assez longtemps pour que l'inspiration vous trouve. Le pire qui puisse arriver est que l'écran s'éteigne. L'API Wake Lock peut empêcher cela.
La première étape consiste à obtenir un verrouillage de réveil avec navigator.wakelock.request method().
Je lui transmets la chaîne 'screen' pour obtenir un verrouillage de réveil de l'écran.
J'ajoute ensuite un écouteur d'événements pour être informé lorsque le verrouillage de l'activation est libéré.
Cela peut se produire, par exemple, lorsque la visibilité de l'onglet change.
Si cela se produit, je peux, lorsque l'onglet redevient visible, récupérer le verrouillage de l'activation.
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);
Oui, il s'agit d'une amélioration progressive. Je n'ai donc besoin de la charger que lorsque le navigateur est compatible avec l'API.
if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
import('./wake_lock.mjs');
}
Dans Fugu Greetings, une case à cocher Insomnia permet de maintenir l'écran allumé.
API Idle Detection
Parfois, même si vous fixez l'écran pendant des heures, vous ne trouvez aucune idée pour votre carte de vœux. L'API Idle Detection permet à l'application de détecter le temps d'inactivité de l'utilisateur. Si l'utilisateur reste inactif trop longtemps, l'application revient à son état initial et efface le canevas. Cette API est protégée par l'autorisation de notification, car de nombreux cas d'utilisation en production de la détection d'inactivité sont liés aux notifications. Par exemple, pour n'envoyer une notification qu'à un appareil que l'utilisateur utilise activement.
Après m'être assuré que l'autorisation de notification est accordée, j'instancie le détecteur d'inactivité. J'enregistre un écouteur d'événements qui écoute les changements d'inactivité, y compris l'état de l'utilisateur et de l'écran. L'utilisateur peut être actif ou inactif, et l'écran peut être déverrouillé ou verrouillé. Si l'utilisateur est inactif, le canevas est effacé. Je définis un seuil de 60 secondes pour le détecteur d'inactivité.
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,
});
Et comme toujours, je ne charge ce code que lorsque le navigateur le prend en charge.
if ('IdleDetector' in window) {
import('./idle_detection.mjs');
}
Dans l'application Fugu Greetings, le canevas se vide lorsque la case à cocher Ephemeral est cochée et que l'utilisateur est inactif trop longtemps.
Conclusion
Ouf, quelle aventure ! Tant d'API dans une seule application exemple. Et n'oubliez pas que je ne fais jamais payer à l'utilisateur le coût de téléchargement d'une fonctionnalité que son navigateur ne prend pas en charge. En utilisant l'amélioration progressive, je m'assure que seul le code pertinent est chargé. Comme les requêtes sont peu coûteuses avec HTTP/2, ce modèle devrait bien fonctionner pour de nombreuses applications. Toutefois, vous pouvez envisager d'utiliser un bundler pour les applications très volumineuses.
L'appli peut sembler légèrement différente sur chaque navigateur, car toutes les plates-formes ne sont pas compatibles avec toutes les fonctionnalités. Toutefois, la fonctionnalité de base est toujours présente et est améliorée progressivement en fonction des capacités du navigateur concerné. Ces fonctionnalités peuvent changer même dans un même navigateur, selon que l'application s'exécute en tant qu'application installée ou dans un onglet de navigateur.
Vous pouvez dupliquer Fugu sur GitHub.
L'équipe Chromium s'efforce de rendre les API Fugu avancées plus attrayantes. En appliquant l'amélioration progressive lors de la création de mon application, je m'assure que tout le monde bénéficie d'une expérience de base solide et de qualité, mais que les personnes utilisant des navigateurs compatibles avec davantage d'API de plate-forme Web bénéficient d'une expérience encore meilleure. J'ai hâte de voir ce que vous allez faire avec l'amélioration progressive dans vos applications.
Remerciements
Je remercie Christian Liebel et Hemanth HM qui ont tous deux contribué à Fugu Greetings.
Ce document a été examiné par Joe Medley et Kayce Basques.
Jake Archibald m'a aidé à comprendre la situation avec import() dynamique dans un contexte de service worker.