Passer au contenu principal




Construire pour les navigateurs modernes et s'améliorer progressivement comme en 2003


Mise à jour

Apparaît dans:
Applications Web progressives

De retour en mars 2003, Nick Fink et
Steve Champeon a stupéfié le monde de la conception Web
avec le concept de
amélioration progressive,
une stratégie de conception Web qui met l'accent sur le chargement du contenu principal de la page Web en premier,
et cela ajoute progressivement plus de nuances
et des couches de présentation et de fonctionnalités techniquement rigoureuses en plus du contenu.
Alors qu'en 2003, l'amélioration progressive consistait à utiliser - à l'époque - des
Fonctionnalités CSS, JavaScript discret et même simplement des graphiques vectoriels évolutifs.
L'amélioration progressive en 2020 et au-delà consiste à utiliser
capacités de navigateur modernes.

100002010000053c000003e8b978fe17e590bc9a-2242580
Diapositive: Conception Web inclusive pour l'avenir avec amélioration progressive.
(La source)

JavaScript moderne

En parlant de JavaScript, la situation de prise en charge du navigateur pour le dernier noyau JavaScript ES 2015
les fonctionnalités sont excellentes.
Le nouveau standard comprend des promesses, des modules, des classes, des modèles littéraux, des fonctions fléchées, laisser et const,
paramètres par défaut, générateurs, affectation de déstructuration, repos et diffusion, Carte/Ensemble,
WeakMap/WeakSet, et beaucoup plus.
Tous sont pris en charge.

10000000000009c40000039ef0a6fe5c50e42dea-1991285
Le tableau de prise en charge du navigateur ECMAScript 2015 (ES6). (La source)

Fonctions Async, une fonctionnalité ES 2017 et l'un de mes favoris personnels,
peut être utilisé
dans tous les principaux navigateurs.
le asynchrone et attendre les mots clés permettent un comportement asynchrone basé sur des promesses
à écrire dans un style plus propre, évitant d'avoir à configurer explicitement les chaînes de promesses.

10000000000009c400000304a19ef0ff1d72987d-1299321
Tableau de prise en charge du navigateur de fonctions Async. (La source)

Et même des ajouts linguistiques très récents pour l'ES 2020 comme
chaînage optionnel et
fusion nulle
ont atteint le support très rapidement. Vous pouvez voir un exemple de code ci-dessous.
En ce qui concerne les fonctionnalités JavaScript de base, l'herbe ne pourrait pas être beaucoup plus verte qu'elle
c'est aujourd'hui.

const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah',
},
};
console.log(adventurer.dog?.name);
console.log(0 ?? 42);

1000020100000640000003e810e16d93c747b2d5-1385902
L'herbe est verte en ce qui concerne les fonctionnalités de base de JavaScript.
(Capture d'écran du produit Microsoft, utilisée avec
autorisation.)

L'exemple d'application: Fugu Greetings

Pour cet article, je travaille avec une simple PWA, appelée
Salutations Fugu
(GitHub).
Le nom de cette application est une pointe du chapeau pour Project Fugu 🐡, un effort pour donner tout au Web
les pouvoirs des applications natives.
Vous pouvez en savoir plus sur le projet sur sa
page de destination.

Fugu Greetings est une application de dessin qui vous permet de créer des cartes de vœux virtuelles et d'envoyer
les à vos proches. Il illustre
Les concepts de base de PWA. C'est
fiable et entièrement hors ligne, donc même si vous ne le faites pas
avoir un réseau, vous pouvez toujours l'utiliser. Il est également installable
à l'écran d'accueil d'un appareil et s'intègre parfaitement au système d'exploitation
en tant qu'application autonome.

10000201000009c4000006a2f58b840608cea761-4971462
le Salutations Fugu exemple d'application.

Amélioration progressive

Avec cela à l'écart, il est temps d'en parler amélioration progressive.
Glossaire MDN Web Docs vous définissez
le concept comme suit:

L'amélioration progressive est une philosophie de conception qui fournit une base de
contenu et fonctionnalités essentiels au plus grand nombre d'utilisateurs possible, tout en
offrir la meilleure expérience possible uniquement aux utilisateurs des plus modernes
navigateurs capables d'exécuter tout le code requis.

Détection des fonctionnalités
est généralement utilisé pour déterminer si les navigateurs peuvent gérer des fonctionnalités plus modernes,
tandis que 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 le développement des meilleurs sites Web possibles tout en faisant fonctionner ces sites Web
sur plusieurs agents utilisateurs inconnus.
Dégradation progressive
est lié, mais n'est pas la même chose et est souvent considéré comme allant dans la direction opposée
à l'amélioration progressive.
En réalité, les deux approches sont valables et peuvent souvent se compléter.


Contributeurs MDN

Commencer chaque carte de voeux à partir de zéro peut être vraiment fastidieux.
Alors pourquoi ne pas avoir une fonctionnalité qui permet aux utilisateurs d'importer une image, et de partir de là?
Avec une approche traditionnelle, vous auriez utilisé un

élément pour y arriver.
Tout d'abord, vous créez l'élément, définissez son taper à 'déposer' et ajoutez des types MIME au J'accepte propriété,
puis "cliquez" dessus par programme et écoutez les changements.
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();
});
};

Quand il y a un importer caractéristique, il devrait probablement y avoir un exportation fonctionnalité
afin que les utilisateurs puissent enregistrer leurs cartes de vœux localement.
La manière traditionnelle d'enregistrer des fichiers consiste à créer un lien d'ancrage
avec un Télécharger
attribut et avec une URL blob comme son href.
Vous devez également "cliquer" par programmation dessus pour déclencher le téléchargement,
et, pour éviter les fuites de mémoire, n'oubliez pas de révoquer l'URL de l'objet blob.

const exportImage = async ( blob ) => {
const a = document . createElement ( 'a' ) ;
a . download = 'fugu-salutation.png' ;
a . href = URL . createObjectURL ( blob ) ;
a . addEventListener ( 'clic' , ( e ) => {
setTimeout ( ( ) => URL . revokeObjectURL ( a . href ) , 30 * 1000 ) ;
} ) ;
a . cliquez sur ( ) ;
} ;

Mais attendez une minute. Mentalement, vous n'avez pas «téléchargé» de carte de vœux, vous avez
«Sauvé».
Plutôt que de vous montrer une boîte de dialogue «enregistrer» qui vous permet de choisir où placer le fichier,
le navigateur a téléchargé directement la carte de vœux sans intervention de l'utilisateur
et l'a mis directement dans votre dossier Téléchargements. Ce n'est pas génial.

Et s'il y avait un meilleur moyen?
Et si vous pouviez simplement ouvrir un fichier local, le modifier, puis enregistrer les modifications,
soit vers un nouveau fichier, soit retour au fichier d'origine que vous aviez initialement ouvert?
Il s'avère qu'il y en a. L'API du système de fichiers natif
vous permet d'ouvrir et de créer des fichiers et
répertoires, ainsi que les modifier et les enregistrer.

Alors, comment puis-je détecter les fonctionnalités d'une API?
L'API Native File System 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. J'ai montré comment procéder ci-dessous.

const loadImportAndExport = ( ) => {
if ( 'chooseFileSystemEntries' dans la fenêtre ) {
Promesse . tout ( [
import ( './import_image.mjs' ) ,
import ( './export_image.mjs' ) ,
] ) ;
} else {
Promesse . tout ( [
import ( './import_image_legacy.mjs' ) ,
import ( './export_image_legacy.mjs' ) ,
] ) ;
}
} ;

Mais avant de plonger dans les détails de l'API Native File System,
permettez-moi de souligner rapidement le modèle d'amélioration progressive ici.
Sur les navigateurs qui ne prennent actuellement pas en charge l'API Native File System, je charge les scripts hérités.
Vous pouvez voir les onglets réseau de Firefox et Safari ci-dessous.

100002010000058c000000ca65613fec1d7fb3e2-1128402
Onglet réseau Safari Web Inspector.
10000201000005800000012430b7b8786bf315dd-5981940
Onglet réseau des outils de développement Firefox.

Cependant, sur Chrome, un navigateur prenant en charge l'API, seuls les nouveaux scripts sont chargés.
Ceci est rendu possible avec élégance grâce à
dynamique importer (), que tous les navigateurs modernes
Support.
Comme je l'ai dit plus tôt, l'herbe est assez verte ces jours-ci.

10000201000006fc0000021ad16a28ba8f5cbf1a-3614115
Onglet réseau Chrome DevTools.

L'API du système de fichiers natif

Alors maintenant que j'ai abordé ce problème, il est temps d'examiner l'implémentation réelle basée sur l'API Native File System.
Pour importer une image, j'appelle window.chooseFileSystemEntries ()
et passez-le un accepte propriété où je dis que je veux des fichiers image.
Les extensions de fichier ainsi que les types MIME sont pris en charge.
Cela se traduit par un descripteur de fichier, à partir duquel je peux obtenir le fichier réel en appelant getFile ().

const importImage = async ( ) => {
essayez {
poignée de const = fenêtre d' attente . chooseFileSystemEntries ( {
accepte : [
{
description : 'Fichiers image' ,
mimeTypes : [ 'image / *' ] ,
extensions : [ 'jpg' , 'jpeg' , 'png' , 'webp' , 'svg' ] ,
} ,
] ,
} ) ;
poignée de retour . getFile ( ) ;
} catch ( err ) {
console . erreur (. err nom, err message.);
}
} ;

L'exportation d'une image est presque la même, mais cette fois
J'ai besoin de passer un paramètre de type de 'save-file' au chooseFileSystemEntries () méthode.
De cela, j'obtiens une boîte de dialogue d'enregistrement de fichier.
Avec le fichier ouvert, ce n'était pas nécessaire car 'fichier ouvert' est la valeur par défaut.
J'ai mis le accepte paramètre similaire à avant, mais cette fois limité aux images PNG.
Encore une fois, je récupère un descripteur de fichier, mais plutôt que de récupérer le fichier,
cette fois, je crée 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 inscriptible.

Tout peut toujours échouer: le disque est peut-être à court d'espace,
il peut y avoir une erreur d'écriture ou de lecture, ou peut-être simplement que l'utilisateur annule la boîte de dialogue de fichier.
C'est pourquoi j'enroule toujours les appels dans un essayez ... attrapez déclaration.

const exportImage = async ( blob ) => {
essayez {
poignée de const = fenêtre d' attente . chooseFileSystemEntries ( {
tapez : 'save-file' ,
accepte : [
{
description : 'Fichier image' ,
extensions : [ 'png' ] ,
mimeTypes : [ 'image / png' ] ,
} ,
] ,
} ) ;
const writable = wait handle . createWritable ( ) ;
attendre inscriptible . écrire ( blob ) ;
attendre inscriptible . fermer ( ) ;
} catch ( err ) {
console . erreur (. err nom, err message.);
}
} ;

Utilisation de l'amélioration progressive avec l'API Native File System,
Je peux ouvrir un fichier comme avant.
Le fichier importé est dessiné directement sur le canevas.
Je peux faire mes modifications et enfin les enregistrer avec une vraie boîte de dialogue de sauvegarde
où je peux choisir le nom et l'emplacement de stockage du fichier.
Le fichier est maintenant prêt à être conservé pour l'éternité.

10000201000009c4000005db39851711e3cb3bf0-1180658
La boîte de dialogue d'ouverture de fichier.
10000201000009c4000005db01941257d7be6a85-4907083
L'image importée.
10000201000009c4000005dbcbe1b58ec828bb29-9444761
Enregistrement de l'image modifiée dans un nouveau fichier.

Les API cibles de partage Web et de partage Web

Mis à part le stockage pour l'éternité, je veux peut-être partager ma carte de voeux.
C'est quelque chose que l'API Web Share et
L'API cible de partage Web me permet de le faire.
Les systèmes d'exploitation mobiles et, plus récemment, de bureau ont gagné le partage natif
mécanismes.
Par exemple, ci-dessous se trouve la feuille de partage de Safari de bureau sur macOS déclenchée à partir d'un article sur
ma Blog.
Lorsque vous cliquez sur le Partager l'article bouton, vous pouvez partager un lien vers l'article avec un ami, pour
exemple, via l'application native macOS Messages.

1000020100000356000001c434f5dd5d64721768-4082576
API de partage Web sur Safari de bureau sur macOS.

Le code pour y parvenir est assez simple. J'appelle navigator.share () et
passer un optionnel Titre, texte, et URL dans un objet.
Mais que faire si je veux joindre une image? Le niveau 1 de l'API Web Share ne le prend pas encore en charge.
La bonne nouvelle est que Web Share Level 2 a ajouté des fonctionnalités de partage de fichiers.

essayez {
attendre le navigateur . partager ( {
title : 'Consultez cet article:' ,
texte : ` " $ { document . title } "par @tomayac: ` ,
url : document . querySelector ( 'link [rel = canonical]' ) . href ,
} ) ;
} catch ( err ) {
console . mettre en garde (err nom, err message..);
}

Laissez-moi vous montrer comment faire fonctionner cela avec l'application de carte de voeux Fugu.
Tout d'abord, je dois préparer un Les données objet avec un des dossiers tableau constitué d'un objet blob, puis
à Titre et un texte. Ensuite, comme meilleure pratique, j'utilise le nouveau navigator.canShare () méthode qui fait
ce que son nom suggère:
Il me dit si le Les données l'objet que j'essaie de partager peut techniquement être partagé par le navigateur.
Si navigator.canShare () me dit que les données peuvent être partagées, je suis prêt à
appel navigator.share () comme avant.
Parce que tout peut échouer, j'utilise à nouveau un essayez ... attrapez bloquer.

const share = async ( titre , texte , blob ) => {
const data = {
fichiers : [
nouveau fichier ( [ blob ] , 'fugu-salutation.png' , {
type : blob . type ,
} ) ,
] ,
title : titre ,
texte : texte ,
} ;
essayez {
if ( ! ( navigateur . canShare ( données ) ) ) {
throw new Error ( "Can't share data." , data ) ;
}
attendre le navigateur . partager ( données ) ;
} catch ( err ) {
console . erreur (. err nom, err message.);
}
} ;

Comme avant, j'utilise l'amélioration progressive.
Si les deux «Compartir» et 'peut partager' existent sur le navigateur objet, alors seulement j'avance et
charge share.mjs via dynamique importer ().
Sur les navigateurs comme Safari mobile qui ne remplissent qu'une des deux conditions, je ne charge pas
la fonctionnalité.

const loadShare = ( ) => {
if ( 'partager' dans le navigateur && 'canShare' dans le navigateur ) {
import ( './share.mjs' ) ;
}
} ;

Dans Fugu Greetings, si je touche le Compartir bouton sur un navigateur compatible comme Chrome sur Android,
la feuille de partage native s'ouvre.
Je peux, par exemple, choisir Gmail, et le widget de composition d'e-mails apparaît avec le
image jointe.

10000201000003e4000008004d3aea65db2aba6c-7000463
Choisir une application avec laquelle partager le fichier.
10000201000003e400000800d873c982e6d44c89-9083099
Le fichier est joint à un nouvel e-mail dans l'éditeur de Gmail.

Ensuite, je veux parler des contacts, c'est-à-dire du carnet d'adresses d'un appareil
ou l'application de gestion de contacts.
Lorsque vous écrivez une carte de vœux, il n'est pas toujours facile d'écrire correctement
le nom de quelqu'un.
Par exemple, j'ai un ami Sergey qui préfère que son nom soit épelé en lettres cyrilliques. je suis
en utilisant un clavier allemand QWERTZ et je ne sais pas comment taper leur nom.
C'est un problème que l'API Contact Picker peut résoudre.
Puisque mon ami est stocké dans l'application Contacts de mon téléphone,
via l'API Contacts Picker, je peux accéder à mes contacts sur le Web.

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 e-mails, l'avatar
icônes ou adresses physiques.
Ensuite, je configure un options objet et ensemble plusieurs à vrai, afin que je puisse sélectionner plus
plus d'une entrée.
Enfin, je peux appeler navigator.contacts.select (), qui renvoie les propriétés souhaitées
pour les contacts sélectionnés par l'utilisateur.

const getContacts = async ( ) => {
propriétés const = [ 'nom' ] ;
options const = { multiple : vrai } ;
essayez {
retour attendre le navigateur . contacts . sélectionnez ( propriétés , options ) ;
} catch ( err ) {
console . erreur (. err nom, err message.);
}
} ;

Et maintenant, vous avez probablement appris le modèle:
Je ne charge le fichier que lorsque l'API est réellement prise en charge.

if ( 'contacts' dans le navigateur ) {
import ( './contacts.mjs' ) ;
}

Dans Fugu Greeting, lorsque je touche le Contacts bouton et sélectionnez mes deux meilleurs copains,
Сергей Михайлович Брин et 劳伦斯 · 爱德华 · »拉里» · 佩奇,
vous pouvez voir comment le
le sélecteur de contacts est limité à afficher uniquement leurs noms,
mais pas leurs adresses e-mail ou d'autres informations telles que leurs numéros de téléphone.
Leurs noms sont ensuite dessinés sur ma carte de voeux.

10000201000003e400000800ad376e3b4b3386c6-2969667
Sélection de deux noms avec le sélecteur de contacts dans le carnet d'adresses.
1000000000000438000008ac945569e52ce934fa-7828050
Les deux noms sont ensuite dessinés sur la carte de voeux.

L'API du Presse-papiers asynchrone

La prochaine étape consiste à copier et coller.
L'une de nos opérations préférées en tant que développeurs de logiciels est le copier-coller.
En tant qu'auteur de cartes de vœux, j'ai parfois envie de faire de même.
Je peux soit coller une image dans une carte de vœux sur laquelle je travaille,
ou copiez ma carte de vœux pour que je puisse continuer à la modifier à partir de
ailleurs.
L'API du Presse-papiers Async,
prend en charge le texte et les images.
Laissez-moi vous expliquer comment j'ai ajouté la prise en charge du copier-coller au Fugu
Salutations app.

Pour copier quelque chose dans le presse-papiers du système, je dois y écrire.
le navigator.clipboard.write () prend un tableau d'éléments de presse-papiers comme un
paramètre.
Chaque élément du presse-papiers est essentiellement un objet avec un blob comme valeur et le type de blob
comme clé.

copie de const = async ( blob ) => {
essayez {
attendre le navigateur . presse-papiers . écrire ( [
nouveau ClipboardItem ( {
[ blob . type ] : blob ,
} ) ,
] ) ;
} catch ( err ) {
console . erreur (. err nom, err message.);
}
} ;

Pour coller, je dois faire une boucle sur les éléments du presse-papiers que j'obtiens en appelant
navigator.clipboard.read ().
La raison en est que plusieurs éléments du presse-papiers peuvent se trouver dans le presse-papiers dans
différentes représentations.
Chaque élément du presse-papiers a un les types champ qui m'indique les types MIME des
Ressources.
J'appelle l'élément du presse-papiers getType () méthode, en passant la
Type MIME que j'ai obtenu auparavant.

const paste = async ( ) => {
essayez {
const clipboardItems = attendre le navigateur . presse-papiers . lire ( ) ;
for ( const clipboardItem of clipboardItems ) {
essayez {
for ( type const de clipboardItem . types ) {
const blob = attendre clipboardItem . getType ( type ) ;
return blob ;
}
} catch ( err ) {
console . erreur (. err nom, err message.);
}
}
} catch ( err ) {
console . erreur (. err nom, err message.);
}
} ;

Et c'est presque inutile de le dire maintenant. Je ne fais cela que sur les navigateurs compatibles.

if ( 'clipboard' dans le navigateur && 'write' dans le navigateur . clipboard ) {
import ( './clipboard.mjs' ) ;
}

Alors, comment cela fonctionne-t-il dans la pratique? J'ai une image ouverte dans l'application macOS Preview et
copiez-le dans le presse-papiers.
Quand je clique Pâte, l'application Fugu Greetings me demande alors
si je veux autoriser l'application à voir le texte et les images dans le presse-papiers.

10000201000009c4000006a2fe8ceb5ee2fb3f83-8002472
L'invite d'autorisation du presse-papiers.

Enfin, après avoir accepté l'autorisation, l'image est ensuite collée dans l'application.
L'inverse fonctionne aussi.
Permettez-moi de copier une carte de vœux dans le presse-papiers.
Lorsque j'ouvre ensuite Aperçu et clique sur Déposer et alors Nouveau à partir du presse-papiers,
la carte de voeux est collée dans une nouvelle image sans titre.

10000201000009c4000005a941a3287f1a5052a8-7523602
Une image collée dans l'application macOS Preview.

L'API Badging

Une autre API utile est l'API Badging.
En tant que PWA installable, Fugu Greetings a bien sûr une icône d'application
que les utilisateurs peuvent placer sur la station d'accueil de l'application ou sur l'écran d'accueil.
Un moyen amusant et simple de démontrer l'API est de (ab) l'utiliser dans Fugu Greetings
comme compteur de coups de stylo.
J'ai ajouté un écouteur d'événement qui incrémente le compteur de traits de stylet chaque fois que le pointeur vers le bas l'événement se produit
puis définit le badge d'icône mis à jour.
Chaque fois que la toile est effacée, le compteur se réinitialise 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, donc la logique de chargement est comme d'habitude.

if ( 'setAppBadge' dans le navigateur ) {
import ( './badge.mjs' ) ;
}

Dans cet exemple, j'ai dessiné les nombres de un à sept, en utilisant un trait de stylo
par numéro.
Le compteur de badges sur l'icône est maintenant à sept.

10000201000009c4000005dbe9bf427abd63947a-3978772
Dessinez les nombres de 1 à 7, en utilisant sept traits de stylo.
10000201000002e6000001c0855aa2df1e30228c-3334591
Le compteur de coups de stylo sous la forme du badge d'icône d'application.

L'API de synchronisation en arrière-plan périodique

Vous voulez commencer chaque journée avec quelque chose de nouveau?
Une caractéristique intéressante de l'application Fugu Greetings est qu'elle peut vous inspirer chaque matin
avec une nouvelle image de fond pour commencer votre carte de voeux.
L'application utilise l'API Periodic Background Sync
pour y parvenir.

La première étape consiste à S'inscrire un événement de synchronisation périodique dans l'enregistrement du technicien de service.
Il écoute une balise de synchronisation appelée 'image-du-jour'
et a un intervalle minimum d'un jour,
afin que l'utilisateur puisse obtenir une nouvelle image d'arrière-plan toutes les 24 heures.

const registerPeriodicBackgroundSync = async ( ) => {
enregistrement const = attendre le navigateur . serviceWorker . prêt ;
essayez {
inscription . PeriodicSync . register ( 'image-du-jour-sync' , {
minInterval : 24 * 60 * 60 * 1000 ,
} ) ;
} catch ( err ) {
console . erreur (. err nom, err message.);
}
} ;

La deuxième étape consiste à Ecoutez pour le periodicsync événement dans le service worker.
Si la balise d'événement est 'image-du-jour', c'est-à-dire celui qui a été enregistré auparavant,
l'image du jour est récupérée via le getImageOfTheDay () une fonction,
et le résultat s'est propagé à tous les clients, afin qu'ils puissent mettre à jour leurs toiles et
caches.

soi . addEventListener ( 'periodicsync' , ( syncEvent ) => {
if ( syncEvent . tag === 'image-du-jour-sync' ) {
syncEvent . waitUntil (
( async ( ) => {
const blob = attendre getImageOfTheDay ( ) ;
clients const = attendre soi-même . clients . matchAll ( ) ;
clients . forEach ( ( client ) => {
client . postMessage ( {
image : blob ,
} ) ;
} ) ;
} ) ( )
) ;
}
} ) ;

Encore une fois, il s'agit vraiment d'une amélioration progressive, de sorte que le code n'est chargé que lorsque le
L'API est prise en charge par le navigateur.
Cela s'applique à la fois au code client et au code du technicien de service.
Sur les navigateurs non compatibles, aucun d'eux n'est chargé.
Notez comment dans le service worker, au lieu d'une dynamique importer ()
(qui n'est pas pris en charge dans un contexte de service worker
encore),
J'utilise le classique
importScripts ().


enregistrement const = attendre le navigateur . serviceWorker . prêt ;
if ( enregistrement && 'PeriodicSync' dans l' enregistrement ) {
import ( './periodic_background_sync.mjs' ) ;
}


if ( 'PeriodicSync' dans l' auto . enregistrement ) {
importScripts ( './image_of_the_day.mjs' ) ;
}

Dans Fugu Greetings, appuyez sur la touche Fond d'écran le bouton révèle l'image de la carte de voeux du jour
qui est mis à jour chaque jour via l'API Periodic Background Sync.

10000201000009c4000005e0b042853d0e77c224-9063496
En appuyant sur le Fond d'écran Le bouton affiche l'image du jour.

API de déclencheurs de notification

Parfois, même avec beaucoup d'inspiration, vous avez besoin d'un coup de pouce pour terminer une salutation commencée
carte.
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 être poussé à terminer ma carte de vœux.
Quand ce moment viendra, je recevrai une notification indiquant que ma carte de voeux est en attente.

Après avoir demandé l'heure cible,
l'application planifie la notification avec un showTrigger.
Cela peut être un TimestampTrigger avec la date cible précédemment sélectionnée.
La notification de rappel sera déclenchée localement, aucun côté réseau ou serveur n'est nécessaire.

const targetDate = promptTargetDate ( ) ;
if ( targetDate ) {
enregistrement const = attendre le navigateur . serviceWorker . prêt ;
inscription . showNotification ( 'Rappel' , {
tag : 'rappel' ,
body : "Il est temps de finir votre carte de voeux!" ,
showTrigger : nouveau TimestampTrigger ( targetDate ) ,
} ) ;
}

Comme pour tout ce que j'ai montré jusqu'à présent, il s'agit d'une amélioration progressive,
donc le code n'est chargé que conditionnellement.

if ( 'Notification' dans la fenêtre && 'showTrigger' dans Notification . prototype ) {
import ( './notification_triggers.mjs' ) ;
}

Quand je vérifie le Rappel case à cocher dans Fugu Greetings, une invite demande
moi quand je veux qu'on me rappelle de terminer ma carte de vœux.

10000201000009c4000005db0d42f493acfd1b53-3464336
Planification d'une notification locale pour être rappelé pour terminer une carte de vœux.

Lorsqu'une notification planifiée se déclenche dans Fugu Greetings,
il est affiché comme n'importe quelle autre notification, mais comme je l'ai déjà écrit,
il ne nécessitait pas de connexion réseau.

10000201000009c4000005e0b042853d0e77c225-6170541
La notification déclenchée apparaît dans le centre de notifications macOS.

L'API Wake Lock

Je souhaite également inclure l'API Wake Lock.
Parfois, vous avez juste besoin de rester assez longtemps à l'écran jusqu'à l'inspiration
vous embrasse.
Le pire qui puisse arriver est que l'écran s'éteigne.
L'API Wake Lock peut empêcher que cela se produise.

La première étape consiste à obtenir un verrou de réveil avec le navigator.wakelock.request, méthode ().
Je lui passe la ficelle 'écran' pour obtenir un verrouillage de réveil de l'écran.
J'ajoute ensuite un écouteur d'événement pour être informé lorsque le verrou de réveil 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, obtenir à nouveau le verrou de réveil.

laissez wakeLock = null ;
const requestWakeLock = async ( ) => {
wakeLock = attendre le navigateur . wakeLock . request ( 'écran' ) ;
wakeLock . addEventListener ( 'release' , ( ) => {
console . log ( 'Wake Lock a été libéré' ) ;
} ) ;
console . log ( 'Wake Lock est actif' ) ;
} ;

const handleVisibilityChange = ( ) => {
if ( wakeLock ! == null && document . visibilitéState === 'visible' ) {
requestWakeLock ( ) ;
}
} ;

document . addEventListener ( 'visibilitéchange' , 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
prend en charge l'API.

if ( 'wakeLock' dans le navigateur && 'request' dans le navigateur . wakeLock ) {
import ( './wake_lock.mjs' ) ;
}

Dans Fugu Greetings, il y a un insomnie case à cocher qui, lorsqu'elle est cochée, garde le
écran éveillé.

10000201000009c4000005dbcd9d10dd0a745315-7387536
le insomnie la case à cocher garde l'application éveillée.

L'API de détection d'inactivité

Parfois, même si vous regardez l'écran pendant des heures,
c'est tout simplement inutile et vous ne pouvez pas vous faire la moindre idée de ce qu'il faut faire avec votre carte de voeux.
L'API de détection d'inactivité permet à l'application de détecter le temps d'inactivité de l'utilisateur.
Si l'utilisateur est inactif trop longtemps, l'application se réinitialise à l'état initial
et efface la toile.
Cette API est actuellement fermée derrière le
autorisation de notifications,
étant donné que de nombreux cas d'utilisation de la détection d'inactivité en production sont liés aux notifications,
par exemple, pour envoyer une notification uniquement à un appareil que l'utilisateur utilise actuellement activement.

Après m'être assuré que l'autorisation de notification est accordée, j'instancie le
détecteur de ralenti.
J'enregistre un écouteur d'événements qui écoute les modifications inactives, qui inclut l'utilisateur et
l'état 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 s'efface.
Je donne au détecteur de ralenti un seuil de 60 secondes.

const idleDetector = new IdleDetector ( ) ;
idleDetector . addEventListener ( 'changer' , ( ) => {
const userState = idleDetector . userState ;
const screenState = idleDetector . screenState ;
console . log ( ` Changement inactif. $ userState {}, {screenState $}`);
if ( userState === 'idle' ) {
clearCanvas ( ) ;
}
} ) ;

attendez idleDetector . début ( {
seuil : 60000 ,
signal ,
} ) ;

Et comme toujours, je ne charge ce code que lorsque le navigateur le prend en charge.

if ( 'IdleDetector' dans la fenêtre ) {
import ( './idle_detection.mjs' ) ;
}

Dans l'application Fugu Greetings, le canevas s'efface lorsque le Éphémère la case à cocher est
cochée et l'utilisateur est inactif pendant trop longtemps.

10000201000009c4000005dbb930b1d3f8f5d1da-7386592
Quand le Éphémère la case est cochée et l'utilisateur est inactif depuis trop longtemps, le canevas est effacé.

Fermeture

Ouf, quelle balade. Tant d'API dans un seul exemple d'application.
Et, rappelez-vous, je ne fais jamais payer à l'utilisateur le coût du téléchargement
pour une fonctionnalité que leur navigateur ne prend pas en charge.
En utilisant l'amélioration progressive, je m'assure que seul le code pertinent est chargé.
Et comme avec HTTP / 2, les requêtes sont bon marché, ce modèle devrait bien fonctionner pour beaucoup de
applications,
bien que vous souhaitiez peut-être envisager un bundler pour les applications très volumineuses.

10000201000009c4000006e8d776365d6b538c9a-8214623
Onglet Réseau Chrome DevTools affichant uniquement les demandes de fichiers avec du code pris en charge par le navigateur actuel.

L'application peut sembler un peu différente sur chaque navigateur car toutes les plates-formes ne prennent pas en charge toutes les fonctionnalités,
mais la fonctionnalité de base est toujours là - progressivement améliorée en fonction des capacités du navigateur particulier.
Notez que ces fonctionnalités peuvent changer même dans un seul et même navigateur,
selon que l'application s'exécute en tant qu'application installée ou dans un onglet de navigateur.

android-2817055
Salutations Fugu fonctionnant sur Android Chrome.
safari-1435212
Salutations Fugu s'exécutant sur Safari de bureau.
chrome-9897882
Salutations Fugu fonctionnant sur le bureau Chrome.

Si vous êtes intéressé par le Salutations Fugu application,
va chercher et fourchez-le sur GitHub.

10000201000009c4000005fb1ef077cdf01b8588-2947610
Salutations Fugu app sur GitHub.

L'équipe Chromium travaille d'arrache-pied pour rendre l'herbe plus verte en ce qui concerne les API Fugu avancées.
En appliquant une amélioration progressive dans le développement de mon application,
Je m'assure que tout le monde a une bonne et solide expérience de base,
mais que les personnes utilisant des navigateurs prenant en charge davantage d'API de plate-forme Web obtiennent une expérience encore meilleure.
J'ai hâte de voir ce que vous faites avec l'amélioration progressive de vos applications.

Remerciements

Je suis reconnaissant de Christian Liebel et
Hemanth HM qui ont tous deux contribué à Fugu Greetings.
Cet article a été révisé par Joe medley et
Kayce Basques.
Jake Archibald m'a aidé à découvrir la situation
avec dynamique importer () dans un contexte de service worker.




Construire pour les navigateurs modernes et s'améliorer progressivement comme en 2003


Mise à jour

Apparaît dans:
Applications Web progressives

De retour en mars 2003, Nick Fink et
Steve Champeon a stupéfié le monde de la conception Web
avec le concept de
amélioration progressive,
une stratégie de conception Web qui met l'accent sur le chargement du contenu principal de la page Web en premier,
et cela ajoute progressivement plus de nuances
et des couches de présentation et de fonctionnalités techniquement rigoureuses en plus du contenu.
Alors qu'en 2003, l'amélioration progressive consistait à utiliser - à l'époque - des
Fonctionnalités CSS, JavaScript discret et même simplement des graphiques vectoriels évolutifs.
L'amélioration progressive en 2020 et au-delà consiste à utiliser
capacités de navigateur modernes.

100002010000053c000003e8b978fe17e590bc9a-2242580
Diapositive: Conception Web inclusive pour l'avenir avec amélioration progressive.
(La source)

JavaScript moderne

En parlant de JavaScript, la situation de prise en charge du navigateur pour le dernier noyau JavaScript ES 2015
les fonctionnalités sont excellentes.
Le nouveau standard comprend des promesses, des modules, des classes, des modèles littéraux, des fonctions fléchées, laisser et const,
paramètres par défaut, générateurs, affectation de déstructuration, repos et diffusion, Carte/Ensemble,
WeakMap/WeakSet, et beaucoup plus.
Tous sont pris en charge.

10000000000009c40000039ef0a6fe5c50e42dea-1991285
Le tableau de prise en charge du navigateur ECMAScript 2015 (ES6). (La source)

Fonctions Async, une fonctionnalité ES 2017 et l'un de mes favoris personnels,
peut être utilisé
dans tous les principaux navigateurs.
le asynchrone et attendre les mots clés permettent un comportement asynchrone basé sur des promesses
à écrire dans un style plus propre, évitant d'avoir à configurer explicitement les chaînes de promesses.

10000000000009c400000304a19ef0ff1d72987d-1299321
Tableau de prise en charge du navigateur de fonctions Async. (La source)

Et même des ajouts linguistiques très récents pour l'ES 2020 comme
chaînage optionnel et
fusion nulle
ont atteint le support très rapidement. Vous pouvez voir un exemple de code ci-dessous.
En ce qui concerne les fonctionnalités JavaScript de base, l'herbe ne pourrait pas être beaucoup plus verte qu'elle
c'est aujourd'hui.

const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah',
},
};
console.log(adventurer.dog?.name);
console.log(0 ?? 42);

1000020100000640000003e810e16d93c747b2d5-1385902
L'herbe est verte en ce qui concerne les fonctionnalités de base de JavaScript.
(Capture d'écran du produit Microsoft, utilisée avec
autorisation.)

L'exemple d'application: Fugu Greetings

Pour cet article, je travaille avec une simple PWA, appelée
Salutations Fugu
(GitHub).
Le nom de cette application est une pointe du chapeau pour Project Fugu 🐡, un effort pour donner tout au Web
les pouvoirs des applications natives.
Vous pouvez en savoir plus sur le projet sur sa
page de destination.

Fugu Greetings est une application de dessin qui vous permet de créer des cartes de vœux virtuelles et d'envoyer
les à vos proches. Il illustre
Les concepts de base de PWA. C'est
fiable et entièrement hors ligne, donc même si vous ne le faites pas
avoir un réseau, vous pouvez toujours l'utiliser. Il est également installable
à l'écran d'accueil d'un appareil et s'intègre parfaitement au système d'exploitation
en tant qu'application autonome.

10000201000009c4000006a2f58b840608cea761-4971462
le Salutations Fugu exemple d'application.

Amélioration progressive

Avec cela à l'écart, il est temps d'en parler amélioration progressive.
Glossaire MDN Web Docs vous définissez
le concept comme suit:

L'amélioration progressive est une philosophie de conception qui fournit une base de
contenu et fonctionnalités essentiels au plus grand nombre d'utilisateurs possible, tout en
offrir la meilleure expérience possible uniquement aux utilisateurs des plus modernes
navigateurs capables d'exécuter tout le code requis.

Détection des fonctionnalités
est généralement utilisé pour déterminer si les navigateurs peuvent gérer des fonctionnalités plus modernes,
tandis que 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 le développement des meilleurs sites Web possibles tout en faisant fonctionner ces sites Web
sur plusieurs agents utilisateurs inconnus.
Dégradation progressive
est lié, mais n'est pas la même chose et est souvent considéré comme allant dans la direction opposée
à l'amélioration progressive.
En réalité, les deux approches sont valables et peuvent souvent se compléter.


Contributeurs MDN

Commencer chaque carte de voeux à partir de zéro peut être vraiment fastidieux.
Alors pourquoi ne pas avoir une fonctionnalité qui permet aux utilisateurs d'importer une image, et de partir de là?
Avec une approche traditionnelle, vous auriez utilisé un

élément pour y arriver.
Tout d'abord, vous créez l'élément, définissez son taper à 'déposer' et ajoutez des types MIME au J'accepte propriété,
puis "cliquez" dessus par programme et écoutez les changements.
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();
});
};

Quand il y a un importer caractéristique, il devrait probablement y avoir un exportation fonctionnalité
afin que les utilisateurs puissent enregistrer leurs cartes de vœux localement.
La manière traditionnelle d'enregistrer des fichiers consiste à créer un lien d'ancrage
avec un Télécharger
attribut et avec une URL blob comme son href.
Vous devez également "cliquer" par programmation dessus pour déclencher le téléchargement,
et, pour éviter les fuites de mémoire, n'oubliez pas de révoquer l'URL de l'objet blob.

const exportImage = async ( blob ) => {
const a = document . createElement ( 'a' ) ;
a . download = 'fugu-salutation.png' ;
a . href = URL . createObjectURL ( blob ) ;
a . addEventListener ( 'clic' , ( e ) => {
setTimeout ( ( ) => URL . revokeObjectURL ( a . href ) , 30 * 1000 ) ;
} ) ;
a . cliquez sur ( ) ;
} ;

Mais attendez une minute. Mentalement, vous n'avez pas «téléchargé» de carte de vœux, vous avez
«Sauvé».
Plutôt que de vous montrer une boîte de dialogue «enregistrer» qui vous permet de choisir où placer le fichier,
le navigateur a téléchargé directement la carte de vœux sans intervention de l'utilisateur
et l'a mis directement dans votre dossier Téléchargements. Ce n'est pas génial.

Et s'il y avait un meilleur moyen?
Et si vous pouviez simplement ouvrir un fichier local, le modifier, puis enregistrer les modifications,
soit vers un nouveau fichier, soit retour au fichier d'origine que vous aviez initialement ouvert?
Il s'avère qu'il y en a. L'API du système de fichiers natif
vous permet d'ouvrir et de créer des fichiers et
répertoires, ainsi que les modifier et les enregistrer.

Alors, comment puis-je détecter les fonctionnalités d'une API?
L'API Native File System 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. J'ai montré comment procéder ci-dessous.

const loadImportAndExport = ( ) => {
if ( 'chooseFileSystemEntries' dans la fenêtre ) {
Promesse . tout ( [
import ( './import_image.mjs' ) ,
import ( './export_image.mjs' ) ,
] ) ;
} else {
Promesse . tout ( [
import ( './import_image_legacy.mjs' ) ,
import ( './export_image_legacy.mjs' ) ,
] ) ;
}
} ;

Mais avant de plonger dans les détails de l'API Native File System,
permettez-moi de souligner rapidement le modèle d'amélioration progressive ici.
Sur les navigateurs qui ne prennent actuellement pas en charge l'API Native File System, je charge les scripts hérités.
Vous pouvez voir les onglets réseau de Firefox et Safari ci-dessous.

100002010000058c000000ca65613fec1d7fb3e2-1128402
Onglet réseau Safari Web Inspector.
10000201000005800000012430b7b8786bf315dd-5981940
Onglet réseau des outils de développement Firefox.

Cependant, sur Chrome, un navigateur prenant en charge l'API, seuls les nouveaux scripts sont chargés.
Ceci est rendu possible avec élégance grâce à
dynamique importer (), que tous les navigateurs modernes
Support.
Comme je l'ai dit plus tôt, l'herbe est assez verte ces jours-ci.

10000201000006fc0000021ad16a28ba8f5cbf1a-3614115
Onglet réseau Chrome DevTools.

L'API du système de fichiers natif

Alors maintenant que j'ai abordé ce problème, il est temps d'examiner l'implémentation réelle basée sur l'API Native File System.
Pour importer une image, j'appelle window.chooseFileSystemEntries ()
et passez-le un accepte propriété où je dis que je veux des fichiers image.
Les extensions de fichier ainsi que les types MIME sont pris en charge.
Cela se traduit par un descripteur de fichier, à partir duquel je peux obtenir le fichier réel en appelant getFile ().

const importImage = async ( ) => {
essayez {
poignée de const = fenêtre d' attente . chooseFileSystemEntries ( {
accepte : [
{
description : 'Fichiers image' ,
mimeTypes : [ 'image / *' ] ,
extensions : [ 'jpg' , 'jpeg' , 'png' , 'webp' , 'svg' ] ,
} ,
] ,
} ) ;
poignée de retour . getFile ( ) ;
} catch ( err ) {
console . erreur (. err nom, err message.);
}
} ;

L'exportation d'une image est presque la même, mais cette fois
J'ai besoin de passer un paramètre de type de 'save-file' au chooseFileSystemEntries () méthode.
De cela, j'obtiens une boîte de dialogue d'enregistrement de fichier.
Avec le fichier ouvert, ce n'était pas nécessaire car 'fichier ouvert' est la valeur par défaut.
J'ai mis le accepte paramètre similaire à avant, mais cette fois limité aux images PNG.
Encore une fois, je récupère un descripteur de fichier, mais plutôt que de récupérer le fichier,
cette fois, je crée 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 inscriptible.

Tout peut toujours échouer: le disque est peut-être à court d'espace,
il peut y avoir une erreur d'écriture ou de lecture, ou peut-être simplement que l'utilisateur annule la boîte de dialogue de fichier.
C'est pourquoi j'enroule toujours les appels dans un essayez ... attrapez déclaration.

const exportImage = async ( blob ) => {
essayez {
poignée de const = fenêtre d' attente . chooseFileSystemEntries ( {
tapez : 'save-file' ,
accepte : [
{
description : 'Fichier image' ,
extensions : [ 'png' ] ,
mimeTypes : [ 'image / png' ] ,
} ,
] ,
} ) ;
const writable = wait handle . createWritable ( ) ;
attendre inscriptible . écrire ( blob ) ;
attendre inscriptible . fermer ( ) ;
} catch ( err ) {
console . erreur (. err nom, err message.);
}
} ;

Utilisation de l'amélioration progressive avec l'API Native File System,
Je peux ouvrir un fichier comme avant.
Le fichier importé est dessiné directement sur le canevas.
Je peux faire mes modifications et enfin les enregistrer avec une vraie boîte de dialogue de sauvegarde
où je peux choisir le nom et l'emplacement de stockage du fichier.
Le fichier est maintenant prêt à être conservé pour l'éternité.

10000201000009c4000005db39851711e3cb3bf0-1180658
La boîte de dialogue d'ouverture de fichier.
10000201000009c4000005db01941257d7be6a85-4907083
L'image importée.
10000201000009c4000005dbcbe1b58ec828bb29-9444761
Enregistrement de l'image modifiée dans un nouveau fichier.

Les API cibles de partage Web et de partage Web

Mis à part le stockage pour l'éternité, je veux peut-être partager ma carte de voeux.
C'est quelque chose que l'API Web Share et
L'API cible de partage Web me permet de le faire.
Les systèmes d'exploitation mobiles et, plus récemment, de bureau ont gagné le partage natif
mécanismes.
Par exemple, ci-dessous se trouve la feuille de partage de Safari de bureau sur macOS déclenchée à partir d'un article sur
ma Blog.
Lorsque vous cliquez sur le Partager l'article bouton, vous pouvez partager un lien vers l'article avec un ami, pour
exemple, via l'application native macOS Messages.

1000020100000356000001c434f5dd5d64721768-4082576
API de partage Web sur Safari de bureau sur macOS.

Le code pour y parvenir est assez simple. J'appelle navigator.share () et
passer un optionnel Titre, texte, et URL dans un objet.
Mais que faire si je veux joindre une image? Le niveau 1 de l'API Web Share ne le prend pas encore en charge.
La bonne nouvelle est que Web Share Level 2 a ajouté des fonctionnalités de partage de fichiers.

essayez {
attendre le navigateur . partager ( {
title : 'Consultez cet article:' ,
texte : ` " $ { document . title } "par @tomayac: ` ,
url : document . querySelector ( 'link [rel = canonical]' ) . href ,
} ) ;
} catch ( err ) {
console . mettre en garde (err nom, err message..);
}

Laissez-moi vous montrer comment faire fonctionner cela avec l'application de carte de voeux Fugu.
Tout d'abord, je dois préparer un Les données objet avec un des dossiers tableau constitué d'un objet blob, puis
à Titre et un texte. Ensuite, comme meilleure pratique, j'utilise le nouveau navigator.canShare () méthode qui fait
ce que son nom suggère:
Il me dit si le Les données l'objet que j'essaie de partager peut techniquement être partagé par le navigateur.
Si navigator.canShare () me dit que les données peuvent être partagées, je suis prêt à
appel navigator.share () comme avant.
Parce que tout peut échouer, j'utilise à nouveau un essayez ... attrapez bloquer.

const share = async ( titre , texte , blob ) => {
const data = {
fichiers : [
nouveau fichier ( [ blob ] , 'fugu-salutation.png' , {
type : blob . type ,
} ) ,
] ,
title : titre ,
texte : texte ,
} ;
essayez {
if ( ! ( navigateur . canShare ( données ) ) ) {
throw new Error ( "Can't share data." , data ) ;
}
attendre le navigateur . partager ( données ) ;
} catch ( err ) {
console . erreur (. err nom, err message.);
}
} ;

Comme avant, j'utilise l'amélioration progressive.
Si les deux «Compartir» et 'peut partager' existent sur le navigateur objet, alors seulement j'avance et
charge share.mjs via dynamique importer ().
Sur les navigateurs comme Safari mobile qui ne remplissent qu'une des deux conditions, je ne charge pas
la fonctionnalité.

const loadShare = ( ) => {
if ( 'partager' dans le navigateur && 'canShare' dans le navigateur ) {
import ( './share.mjs' ) ;
}
} ;

Dans Fugu Greetings, si je touche le Compartir bouton sur un navigateur compatible comme Chrome sur Android,
la feuille de partage native s'ouvre.
Je peux, par exemple, choisir Gmail, et le widget de composition d'e-mails apparaît avec le
image jointe.

10000201000003e4000008004d3aea65db2aba6c-7000463
Choisir une application avec laquelle partager le fichier.
10000201000003e400000800d873c982e6d44c89-9083099
Le fichier est joint à un nouvel e-mail dans l'éditeur de Gmail.

Ensuite, je veux parler des contacts, c'est-à-dire du carnet d'adresses d'un appareil
ou l'application de gestion de contacts.
Lorsque vous écrivez une carte de vœux, il n'est pas toujours facile d'écrire correctement
le nom de quelqu'un.
Par exemple, j'ai un ami Sergey qui préfère que son nom soit épelé en lettres cyrilliques. je suis
en utilisant un clavier allemand QWERTZ et je ne sais pas comment taper leur nom.
C'est un problème que l'API Contact Picker peut résoudre.
Puisque mon ami est stocké dans l'application Contacts de mon téléphone,
via l'API Contacts Picker, je peux accéder à mes contacts sur le Web.

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 e-mails, l'avatar
icônes ou adresses physiques.
Ensuite, je configure un options objet et ensemble plusieurs à vrai, afin que je puisse sélectionner plus
plus d'une entrée.
Enfin, je peux appeler navigator.contacts.select (), qui renvoie les propriétés souhaitées
pour les contacts sélectionnés par l'utilisateur.

const getContacts = async ( ) => {
propriétés const = [ 'nom' ] ;
options const = { multiple : vrai } ;
essayez {
retour attendre le navigateur . contacts . sélectionnez ( propriétés , options ) ;
} catch ( err ) {
console . erreur (. err nom, err message.);
}
} ;

Et maintenant, vous avez probablement appris le modèle:
Je ne charge le fichier que lorsque l'API est réellement prise en charge.

if ( 'contacts' dans le navigateur ) {
import ( './contacts.mjs' ) ;
}

Dans Fugu Greeting, lorsque je touche le Contacts bouton et sélectionnez mes deux meilleurs copains,
Сергей Михайлович Брин et 劳伦斯 · 爱德华 · »拉里» · 佩奇,
vous pouvez voir comment le
le sélecteur de contacts est limité à afficher uniquement leurs noms,
mais pas leurs adresses e-mail ou d'autres informations telles que leurs numéros de téléphone.
Leurs noms sont ensuite dessinés sur ma carte de voeux.

10000201000003e400000800ad376e3b4b3386c6-2969667
Sélection de deux noms avec le sélecteur de contacts dans le carnet d'adresses.
1000000000000438000008ac945569e52ce934fa-7828050
Les deux noms sont ensuite dessinés sur la carte de voeux.

L'API du Presse-papiers asynchrone

La prochaine étape consiste à copier et coller.
L'une de nos opérations préférées en tant que développeurs de logiciels est le copier-coller.
En tant qu'auteur de cartes de vœux, j'ai parfois envie de faire de même.
Je peux soit coller une image dans une carte de vœux sur laquelle je travaille,
ou copiez ma carte de vœux pour que je puisse continuer à la modifier à partir de
ailleurs.
L'API du Presse-papiers Async,
prend en charge le texte et les images.
Laissez-moi vous expliquer comment j'ai ajouté la prise en charge du copier-coller au Fugu
Salutations app.

Pour copier quelque chose dans le presse-papiers du système, je dois y écrire.
le navigator.clipboard.write () prend un tableau d'éléments de presse-papiers comme un
paramètre.
Chaque élément du presse-papiers est essentiellement un objet avec un blob comme valeur et le type de blob
comme clé.

copie de const = async ( blob ) => {
essayez {
attendre le navigateur . presse-papiers . écrire ( [
nouveau ClipboardItem ( {
[ blob . type ] : blob ,
} ) ,
] ) ;
} catch ( err ) {
console . erreur (. err nom, err message.);
}
} ;

Pour coller, je dois faire une boucle sur les éléments du presse-papiers que j'obtiens en appelant
navigator.clipboard.read ().
La raison en est que plusieurs éléments du presse-papiers peuvent se trouver dans le presse-papiers dans
différentes représentations.
Chaque élément du presse-papiers a un les types champ qui m'indique les types MIME des
Ressources.
J'appelle l'élément du presse-papiers getType () méthode, en passant la
Type MIME que j'ai obtenu auparavant.

const paste = async ( ) => {
essayez {
const clipboardItems = attendre le navigateur . presse-papiers . lire ( ) ;
for ( const clipboardItem of clipboardItems ) {
essayez {
for ( type const de clipboardItem . types ) {
const blob = attendre clipboardItem . getType ( type ) ;
return blob ;
}
} catch ( err ) {
console . erreur (. err nom, err message.);
}
}
} catch ( err ) {
console . erreur (. err nom, err message.);
}
} ;

Et c'est presque inutile de le dire maintenant. Je ne fais cela que sur les navigateurs compatibles.

if ( 'clipboard' dans le navigateur && 'write' dans le navigateur . clipboard ) {
import ( './clipboard.mjs' ) ;
}

Alors, comment cela fonctionne-t-il dans la pratique? J'ai une image ouverte dans l'application macOS Preview et
copiez-le dans le presse-papiers.
Quand je clique Pâte, l'application Fugu Greetings me demande alors
si je veux autoriser l'application à voir le texte et les images dans le presse-papiers.

10000201000009c4000006a2fe8ceb5ee2fb3f83-8002472
L'invite d'autorisation du presse-papiers.

Enfin, après avoir accepté l'autorisation, l'image est ensuite collée dans l'application.
L'inverse fonctionne aussi.
Permettez-moi de copier une carte de vœux dans le presse-papiers.
Lorsque j'ouvre ensuite Aperçu et clique sur Déposer et alors Nouveau à partir du presse-papiers,
la carte de voeux est collée dans une nouvelle image sans titre.

10000201000009c4000005a941a3287f1a5052a8-7523602
Une image collée dans l'application macOS Preview.

L'API Badging

Une autre API utile est l'API Badging.
En tant que PWA installable, Fugu Greetings a bien sûr une icône d'application
que les utilisateurs peuvent placer sur la station d'accueil de l'application ou sur l'écran d'accueil.
Un moyen amusant et simple de démontrer l'API est de (ab) l'utiliser dans Fugu Greetings
comme compteur de coups de stylo.
J'ai ajouté un écouteur d'événement qui incrémente le compteur de traits de stylet chaque fois que le pointeur vers le bas l'événement se produit
puis définit le badge d'icône mis à jour.
Chaque fois que la toile est effacée, le compteur se réinitialise 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, donc la logique de chargement est comme d'habitude.

if ( 'setAppBadge' dans le navigateur ) {
import ( './badge.mjs' ) ;
}

Dans cet exemple, j'ai dessiné les nombres de un à sept, en utilisant un trait de stylo
par numéro.
Le compteur de badges sur l'icône est maintenant à sept.

10000201000009c4000005dbe9bf427abd63947a-3978772
Dessinez les nombres de 1 à 7, en utilisant sept traits de stylo.
10000201000002e6000001c0855aa2df1e30228c-3334591
Le compteur de coups de stylo sous la forme du badge d'icône d'application.

L'API de synchronisation en arrière-plan périodique

Vous voulez commencer chaque journée avec quelque chose de nouveau?
Une caractéristique intéressante de l'application Fugu Greetings est qu'elle peut vous inspirer chaque matin
avec une nouvelle image de fond pour commencer votre carte de voeux.
L'application utilise l'API Periodic Background Sync
pour y parvenir.

La première étape consiste à S'inscrire un événement de synchronisation périodique dans l'enregistrement du technicien de service.
Il écoute une balise de synchronisation appelée 'image-du-jour'
et a un intervalle minimum d'un jour,
afin que l'utilisateur puisse obtenir une nouvelle image d'arrière-plan toutes les 24 heures.

const registerPeriodicBackgroundSync = async ( ) => {
enregistrement const = attendre le navigateur . serviceWorker . prêt ;
essayez {
inscription . PeriodicSync . register ( 'image-du-jour-sync' , {
minInterval : 24 * 60 * 60 * 1000 ,
} ) ;
} catch ( err ) {
console . erreur (. err nom, err message.);
}
} ;

La deuxième étape consiste à Ecoutez pour le periodicsync événement dans le service worker.
Si la balise d'événement est 'image-du-jour', c'est-à-dire celui qui a été enregistré auparavant,
l'image du jour est récupérée via le getImageOfTheDay () une fonction,
et le résultat s'est propagé à tous les clients, afin qu'ils puissent mettre à jour leurs toiles et
caches.

soi . addEventListener ( 'periodicsync' , ( syncEvent ) => {
if ( syncEvent . tag === 'image-du-jour-sync' ) {
syncEvent . waitUntil (
( async ( ) => {
const blob = attendre getImageOfTheDay ( ) ;
clients const = attendre soi-même . clients . matchAll ( ) ;
clients . forEach ( ( client ) => {
client . postMessage ( {
image : blob ,
} ) ;
} ) ;
} ) ( )
) ;
}
} ) ;

Encore une fois, il s'agit vraiment d'une amélioration progressive, de sorte que le code n'est chargé que lorsque le
L'API est prise en charge par le navigateur.
Cela s'applique à la fois au code client et au code du technicien de service.
Sur les navigateurs non compatibles, aucun d'eux n'est chargé.
Notez comment dans le service worker, au lieu d'une dynamique importer ()
(qui n'est pas pris en charge dans un contexte de service worker
encore),
J'utilise le classique
importScripts ().


enregistrement const = attendre le navigateur . serviceWorker . prêt ;
if ( enregistrement && 'PeriodicSync' dans l' enregistrement ) {
import ( './periodic_background_sync.mjs' ) ;
}


if ( 'PeriodicSync' dans l' auto . enregistrement ) {
importScripts ( './image_of_the_day.mjs' ) ;
}

Dans Fugu Greetings, appuyez sur la touche Fond d'écran le bouton révèle l'image de la carte de voeux du jour
qui est mis à jour chaque jour via l'API Periodic Background Sync.

10000201000009c4000005e0b042853d0e77c224-9063496
En appuyant sur le Fond d'écran Le bouton affiche l'image du jour.

API de déclencheurs de notification

Parfois, même avec beaucoup d'inspiration, vous avez besoin d'un coup de pouce pour terminer une salutation commencée
carte.
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 être poussé à terminer ma carte de vœux.
Quand ce moment viendra, je recevrai une notification indiquant que ma carte de voeux est en attente.

Après avoir demandé l'heure cible,
l'application planifie la notification avec un showTrigger.
Cela peut être un TimestampTrigger avec la date cible précédemment sélectionnée.
La notification de rappel sera déclenchée localement, aucun côté réseau ou serveur n'est nécessaire.

const targetDate = promptTargetDate ( ) ;
if ( targetDate ) {
enregistrement const = attendre le navigateur . serviceWorker . prêt ;
inscription . showNotification ( 'Rappel' , {
tag : 'rappel' ,
body : "Il est temps de finir votre carte de voeux!" ,
showTrigger : nouveau TimestampTrigger ( targetDate ) ,
} ) ;
}

Comme pour tout ce que j'ai montré jusqu'à présent, il s'agit d'une amélioration progressive,
donc le code n'est chargé que conditionnellement.

if ( 'Notification' dans la fenêtre && 'showTrigger' dans Notification . prototype ) {
import ( './notification_triggers.mjs' ) ;
}

Quand je vérifie le Rappel case à cocher dans Fugu Greetings, une invite demande
moi quand je veux qu'on me rappelle de terminer ma carte de vœux.

10000201000009c4000005db0d42f493acfd1b53-3464336
Planification d'une notification locale pour être rappelé pour terminer une carte de vœux.

Lorsqu'une notification planifiée se déclenche dans Fugu Greetings,
il est affiché comme n'importe quelle autre notification, mais comme je l'ai déjà écrit,
il ne nécessitait pas de connexion réseau.

10000201000009c4000005e0b042853d0e77c225-6170541
La notification déclenchée apparaît dans le centre de notifications macOS.

L'API Wake Lock

Je souhaite également inclure l'API Wake Lock.
Parfois, vous avez juste besoin de rester assez longtemps à l'écran jusqu'à l'inspiration
vous embrasse.
Le pire qui puisse arriver est que l'écran s'éteigne.
L'API Wake Lock peut empêcher que cela se produise.

La première étape consiste à obtenir un verrou de réveil avec le navigator.wakelock.request, méthode ().
Je lui passe la ficelle 'écran' pour obtenir un verrouillage de réveil de l'écran.
J'ajoute ensuite un écouteur d'événement pour être informé lorsque le verrou de réveil 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, obtenir à nouveau le verrou de réveil.

laissez wakeLock = null ;
const requestWakeLock = async ( ) => {
wakeLock = attendre le navigateur . wakeLock . request ( 'écran' ) ;
wakeLock . addEventListener ( 'release' , ( ) => {
console . log ( 'Wake Lock a été libéré' ) ;
} ) ;
console . log ( 'Wake Lock est actif' ) ;
} ;

const handleVisibilityChange = ( ) => {
if ( wakeLock ! == null && document . visibilitéState === 'visible' ) {
requestWakeLock ( ) ;
}
} ;

document . addEventListener ( 'visibilitéchange' , 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
prend en charge l'API.

if ( 'wakeLock' dans le navigateur && 'request' dans le navigateur . wakeLock ) {
import ( './wake_lock.mjs' ) ;
}

Dans Fugu Greetings, il y a un insomnie case à cocher qui, lorsqu'elle est cochée, garde le
écran éveillé.

10000201000009c4000005dbcd9d10dd0a745315-7387536
le insomnie la case à cocher garde l'application éveillée.

L'API de détection d'inactivité

Parfois, même si vous regardez l'écran pendant des heures,
c'est tout simplement inutile et vous ne pouvez pas vous faire la moindre idée de ce qu'il faut faire avec votre carte de voeux.
L'API de détection d'inactivité permet à l'application de détecter le temps d'inactivité de l'utilisateur.
Si l'utilisateur est inactif trop longtemps, l'application se réinitialise à l'état initial
et efface la toile.
Cette API est actuellement fermée derrière le
autorisation de notifications,
étant donné que de nombreux cas d'utilisation de la détection d'inactivité en production sont liés aux notifications,
par exemple, pour envoyer une notification uniquement à un appareil que l'utilisateur utilise actuellement activement.

Après m'être assuré que l'autorisation de notification est accordée, j'instancie le
détecteur de ralenti.
J'enregistre un écouteur d'événements qui écoute les modifications inactives, qui inclut l'utilisateur et
l'état 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 s'efface.
Je donne au détecteur de ralenti un seuil de 60 secondes.

const idleDetector = new IdleDetector ( ) ;
idleDetector . addEventListener ( 'changer' , ( ) => {
const userState = idleDetector . userState ;
const screenState = idleDetector . screenState ;
console . log ( ` Changement inactif. $ userState {}, {screenState $}`);
if ( userState === 'idle' ) {
clearCanvas ( ) ;
}
} ) ;

attendez idleDetector . début ( {
seuil : 60000 ,
signal ,
} ) ;

Et comme toujours, je ne charge ce code que lorsque le navigateur le prend en charge.

if ( 'IdleDetector' dans la fenêtre ) {
import ( './idle_detection.mjs' ) ;
}

Dans l'application Fugu Greetings, le canevas s'efface lorsque le Éphémère la case à cocher est
cochée et l'utilisateur est inactif pendant trop longtemps.

10000201000009c4000005dbb930b1d3f8f5d1da-7386592
Quand le Éphémère la case est cochée et l'utilisateur est inactif depuis trop longtemps, le canevas est effacé.

Fermeture

Ouf, quelle balade. Tant d'API dans un seul exemple d'application.
Et, rappelez-vous, je ne fais jamais payer à l'utilisateur le coût du téléchargement
pour une fonctionnalité que leur navigateur ne prend pas en charge.
En utilisant l'amélioration progressive, je m'assure que seul le code pertinent est chargé.
Et comme avec HTTP / 2, les requêtes sont bon marché, ce modèle devrait bien fonctionner pour beaucoup de
applications,
bien que vous souhaitiez peut-être envisager un bundler pour les applications très volumineuses.

10000201000009c4000006e8d776365d6b538c9a-8214623
Onglet Réseau Chrome DevTools affichant uniquement les demandes de fichiers avec du code pris en charge par le navigateur actuel.

L'application peut sembler un peu différente sur chaque navigateur car toutes les plates-formes ne prennent pas en charge toutes les fonctionnalités,
mais la fonctionnalité de base est toujours là - progressivement améliorée en fonction des capacités du navigateur particulier.
Notez que ces fonctionnalités peuvent changer même dans un seul et même navigateur,
selon que l'application s'exécute en tant qu'application installée ou dans un onglet de navigateur.

android-2817055
Salutations Fugu fonctionnant sur Android Chrome.
safari-1435212
Salutations Fugu s'exécutant sur Safari de bureau.
chrome-5393316
Salutations Fugu fonctionnant sur le bureau Chrome.

Si vous êtes intéressé par le Salutations Fugu application,
va chercher et fourchez-le sur GitHub.

10000201000009c4000005fb1ef077cdf01b8588-2947610
Salutations Fugu app sur GitHub.

L'équipe Chromium travaille d'arrache-pied pour rendre l'herbe plus verte en ce qui concerne les API Fugu avancées.
En appliquant une amélioration progressive dans le développement de mon application,
Je m'assure que tout le monde a une bonne et solide expérience de base,
mais que les personnes utilisant des navigateurs prenant en charge davantage d'API de plate-forme Web obtiennent une expérience encore meilleure.
J'ai hâte de voir ce que vous faites avec l'amélioration progressive de vos applications.

Remerciements

Je suis reconnaissant de Christian Liebel et
Hemanth HM qui ont tous deux contribué à Fugu Greetings.
Cet article a été révisé par Joe medley et
Kayce Basques.
Jake Archibald m'a aidé à découvrir la situation
avec dynamique importer () dans un contexte de service worker.

R Marketing Numérique