Découvrez les nouvelles et prochaines fonctionnalités de navigation pour votre PWA : From Fugu With Love

1. Avant de commencer

Les applications Web progressives (PWA) sont un type de logiciel d'application fourni sur le Web et créé à l'aide de technologies Web courantes, y compris HTML, CSS et JavaScript. Elles sont conçues pour fonctionner sur n'importe quelle plate-forme utilisant un navigateur conforme aux normes.

Dans cet atelier de programmation, vous allez commencer par une PWA de base, puis explorer de nouvelles fonctionnalités de navigateur qui finiront par donner des super pouvoirs à votre PWA 🦸.

Bon nombre de ces nouvelles fonctionnalités de navigateur sont en cours de développement et de normalisation. Vous devrez donc parfois définir des indicateurs de navigateur pour les utiliser.

Prérequis

Pour cet atelier de programmation, vous devez maîtriser le JavaScript moderne, en particulier les promesses et async/await. Comme toutes les étapes de l'atelier de programmation ne sont pas compatibles avec toutes les plates-formes, il est utile d'avoir d'autres appareils à portée de main pour les tests, par exemple un téléphone Android ou un ordinateur portable utilisant un système d'exploitation différent de celui de l'appareil sur lequel vous modifiez le code. Si vous ne pouvez pas utiliser d'appareils réels, vous pouvez essayer d'utiliser des simulateurs comme le simulateur Android ou des services en ligne comme BrowserStack qui vous permettent de tester votre application depuis votre appareil actuel. Sinon, vous pouvez également ignorer n'importe quelle étape, car elles ne dépendent pas les unes des autres.

Ce que vous allez faire

Vous allez créer une application Web de cartes de vœux et découvrir comment les nouvelles fonctionnalités de navigateur peuvent améliorer votre application pour offrir une expérience avancée sur certains navigateurs (tout en restant utile sur tous les navigateurs modernes).

Vous apprendrez à ajouter des fonctionnalités d'assistance, comme l'accès au système de fichiers, l'accès au presse-papiers système, la récupération des contacts, la synchronisation périodique en arrière-plan, le verrouillage de l'écran, les fonctionnalités de partage et plus encore.

Après avoir suivi l'atelier de programmation, vous comprendrez parfaitement comment améliorer progressivement vos applications Web avec de nouvelles fonctionnalités de navigateur, sans imposer de charge de téléchargement au sous-ensemble de vos utilisateurs qui se trouvent sur des navigateurs incompatibles et, surtout, sans les exclure de votre application.

Prérequis

Voici les navigateurs entièrement compatibles pour le moment :

Il est recommandé d'utiliser le canal de développement spécifique.

2. Projet Fugu

Les applications Progressive Web App (PWA) sont conçues et améliorées à l'aide d'API modernes. Elles offrent des fonctionnalités améliorées, sont fiables et simples à installer, et permettent de toucher tous les utilisateurs sur le Web, partout dans le monde et sur tout type d'appareil.

Certaines de ces API sont très puissantes et, si elles ne sont pas gérées correctement, des problèmes peuvent survenir. Tout comme le poisson-globe 🐡 : si vous le coupez correctement, c'est un délice, mais si vous le coupez mal, il peut être mortel (mais ne vous inquiétez pas, rien ne peut réellement se casser dans cet atelier de programmation).

C'est pourquoi le nom de code interne du projet Web Capabilities (dans lequel les entreprises concernées développent ces nouvelles API) est Project Fugu.

Les fonctionnalités Web permettent déjà aux grandes et petites entreprises de créer des solutions basées uniquement sur le navigateur, ce qui permet souvent un déploiement plus rapide avec des coûts de développement inférieurs par rapport à une approche spécifique à une plate-forme.

3. Commencer

Téléchargez l'un des deux navigateurs, puis définissez l'indicateur d'exécution suivant 🚩 en accédant à about://flags, qui fonctionne à la fois dans Chrome et Edge :

  • #enable-experimental-web-platform-features

Une fois que vous l'avez activée, redémarrez votre navigateur.

Vous allez utiliser la plate-forme Glitch, car elle vous permet d'héberger votre PWA et dispose d'un éditeur correct. Glitch permet également d'importer et d'exporter des données vers GitHub, ce qui évite toute dépendance vis-à-vis d'un fournisseur. Accédez à fugu-paint.glitch.me pour tester l'application. Il s'agit d'une application de dessin basique 🎨 que vous allez améliorer au cours de l'atelier de programmation.

PWA de base Fugu Greetings avec un grand canevas sur lequel est peint le mot "Google".

Après avoir testé l'application, remixez-la pour créer votre propre copie que vous pourrez modifier. L'URL de votre remix ressemblera à glitch.com/edit/#!/bouncy-candytuft (le nom "bouncy-candytuft" sera différent pour vous). Ce remix est directement accessible dans le monde entier. Connectez-vous à votre compte existant ou créez-en un sur Glitch pour enregistrer votre travail. Vous pouvez afficher votre application en cliquant sur le bouton "🕶 Show" (Afficher). L'URL de l'application hébergée ressemblera à bouncy-candytuft.glitch.me (notez le .me au lieu de .com comme domaine de premier niveau).

Vous êtes maintenant prêt à modifier votre application et à l'améliorer. À chaque modification, l'application s'actualise et vos modifications sont visibles directement.

L'IDE Glitch montre la modification d'un document HTML.

Dans l'idéal, les tâches suivantes doivent être effectuées dans l'ordre. Toutefois, comme indiqué ci-dessus, vous pouvez toujours ignorer une étape si vous n'avez pas accès à un appareil compatible. N'oubliez pas que chaque tâche est marquée d'un 🐟, un poisson d'eau douce inoffensif, ou d'un 🐡, un poisson-globe "à manipuler avec précaution", pour vous indiquer si une fonctionnalité est expérimentale ou non.

Consultez la console dans les outils de développement pour voir si une API est compatible avec l'appareil actuel. Nous utilisons également Glitch pour que vous puissiez facilement tester la même application sur différents appareils, par exemple sur votre téléphone mobile et votre ordinateur de bureau.

Compatibilité de l'API enregistrée dans la console des outils de développement.

4. 🐟 Ajouter la prise en charge de l'API Web Share

Créer les dessins les plus incroyables n'a aucun intérêt si personne ne peut les apprécier. Ajoutez une fonctionnalité qui permet à vos utilisateurs de partager leurs dessins avec le monde entier sous forme de cartes de vœux.

L'API Web Share permet de partager des fichiers. Vous vous souvenez peut-être qu'un File n'est qu'un type spécifique de Blob. Par conséquent, dans le fichier share.mjs, importez le bouton de partage et une fonction pratique toBlob() qui convertit le contenu d'un canevas en blob, puis ajoutez la fonctionnalité de partage conformément au code ci-dessous.

Si vous avez implémenté cette fonctionnalité, mais que le bouton ne s'affiche pas, c'est parce que votre navigateur n'implémente pas l'API Web Share.

import { shareButton, toBlob } from './script.mjs';

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);
  }
};

shareButton.style.display = 'block';
shareButton.addEventListener('click', async () => {
  return share('Fugu Greetings', 'From Fugu With Love', await toBlob());
});

5. 🐟 Ajout de la prise en charge de l'API Web Share Target

Vos utilisateurs peuvent désormais partager des cartes de vœux créées à l'aide de l'application. Vous pouvez également leur permettre de partager des images dans votre application et de les transformer en cartes de vœux. Pour ce faire, vous pouvez utiliser l'API Web Share Target.

Dans le fichier manifeste de l'application Web, vous devez indiquer à l'application les types de fichiers que vous pouvez accepter et l'URL que le navigateur doit appeler lorsqu'un ou plusieurs fichiers sont partagés. L'extrait ci-dessous du fichier manifest.webmanifest le montre.

{
  "share_target": {
    "action": "./share-target/",
    "method": "POST",
    "enctype": "multipart/form-data",
    "params": {
      "files": [
        {
          "name": "image",
          "accept": ["image/jpeg", "image/png", "image/webp", "image/gif"]
        }
      ]
    }
  }
}

Le service worker gère ensuite les fichiers reçus. L'URL ./share-target/ n'existe pas réellement. L'application agit simplement dessus dans le gestionnaire fetch et redirige la requête vers l'URL racine en ajoutant un paramètre de requête ?share-target :

self.addEventListener('fetch', (fetchEvent) => {
  /* 🐡 Start Web Share Target */
  if (
    fetchEvent.request.url.endsWith('/share-target/') &&
    fetchEvent.request.method === 'POST'
  ) {
    return fetchEvent.respondWith(
      (async () => {
        const formData = await fetchEvent.request.formData();
        const image = formData.get('image');
        const keys = await caches.keys();
        const mediaCache = await caches.open(
          keys.filter((key) => key.startsWith('media'))[0],
        );
        await mediaCache.put('shared-image', new Response(image));
        return Response.redirect('./?share-target', 303);
      })(),
    );
  }
  /* 🐡 End Web Share Target */

  /* ... */
});

Lorsque l'application se charge, elle vérifie si ce paramètre de requête est défini. Si c'est le cas, elle dessine l'image partagée sur le canevas et la supprime du cache. Tout cela se passe dans script.mjs :

const restoreImageFromShare = async () => {
  const mediaCache = await getMediaCache();
  const image = await mediaCache.match('shared-image');
  if (image) {
    const blob = await image.blob();
    await drawBlob(blob);
    await mediaCache.delete('shared-image');
  }
};

Cette fonction est ensuite utilisée lorsque l'application s'initialise.

if (location.search.includes('share-target')) {
  restoreImageFromShare();
} else {
  drawDefaultImage();
}

6. 🐟 Ajouter la prise en charge de l'importation d'images

Il est difficile de tout dessiner à partir de zéro. Ajoutez une fonctionnalité permettant à vos utilisateurs d'importer une image locale depuis leur appareil dans l'application.

Commencez par lire la fonction drawImage() du canevas. Ensuite, familiarisez-vous avec l'élément <​input
type=file>
.

Fort de ces informations, vous pouvez ensuite modifier le fichier import_image_legacy.mjs et ajouter l'extrait suivant. En haut du fichier, vous importez le bouton d'importation et une fonction pratique drawBlob() qui vous permet de dessiner un blob sur le canevas.

import { importButton, drawBlob } from './script.mjs';

const importImage = async () => {
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'image/png, image/jpeg, image/*';
    input.addEventListener('change', () => {
      const file = input.files[0];
      input.remove();
      return resolve(file);
    });
    input.click();
  });
};

importButton.style.display = 'block';
importButton.addEventListener('click', async () => {
  const file = await importImage();
  if (file) {
    await drawBlob(file);
  }
});

7. 🐟 Ajouter la compatibilité avec l'exportation d'images

Comment votre utilisateur enregistrera-t-il sur son appareil un fichier créé dans l'application ? Traditionnellement, cela se faisait avec un élément <​a
download>
.

Dans le fichier export_image_legacy.mjs, ajoutez le contenu comme indiqué ci-dessous. Importez le bouton d'exportation et une fonction pratique toBlob() qui convertit le contenu du canevas en blob.

import { exportButton, toBlob } from './script.mjs';

export const exportImage = async (blob) => {
  const a = document.createElement('a');
  a.download = 'fugu-greeting.png';
  a.href = URL.createObjectURL(blob);
  a.addEventListener('click', (e) => {
    a.remove();
    setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
  });
  setTimeout(() => a.click(), 0);
};

exportButton.style.display = 'block';
exportButton.addEventListener('click', async () => {
  exportImage(await toBlob());
});

8. 🐟 Ajouter la prise en charge de l'API File System Access

Le partage est une bonne chose, mais vos utilisateurs voudront probablement enregistrer leurs meilleurs travaux sur leurs propres appareils. Ajoutez une fonctionnalité qui permet à vos utilisateurs d'enregistrer (et de rouvrir) leurs dessins.

Auparavant, vous utilisiez une ancienne approche <​input type=file> pour importer des fichiers et une ancienne approche <​a download> pour exporter des fichiers. Vous allez maintenant utiliser l'API File System Access pour améliorer l'expérience.

Cette API permet d'ouvrir et d'enregistrer des fichiers à partir du système de fichiers de l'OS. Modifiez les deux fichiers, import_image.mjs et export_image.mjs respectivement, en ajoutant le contenu ci-dessous. Pour que ces fichiers se chargent, supprimez les emojis 🐡 de script.mjs.

Remplacez cette ligne :

// Remove all the emojis for this feature test to succeed.
if ('show🐡Open🐡File🐡Picker' in window) {
  /* ... */
}

par ce qui suit :

if ('showOpenFilePicker' in window) {
  /* ... */
}

Dans import_image.mjs :

import { importButton, drawBlob } from './script.mjs';

const importImage = async () => {
  try {
    const [handle] = await window.showOpenFilePicker({
      types: [
        {
          description: 'Image files',
          accept: {
            'image/*': ['.png', '.jpg', '.jpeg', '.avif', '.webp', '.svg'],
          },
        },
      ],
    });
    return await handle.getFile();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

importButton.style.display = 'block';
importButton.addEventListener('click', async () => {
  const file = await importImage();
  if (file) {
    await drawBlob(file);
  }
});

Dans export_image.mjs :

import { exportButton, toBlob } from './script.mjs';

const exportImage = async () => {
  try {
    const handle = await window.showSaveFilePicker({
      suggestedName: 'fugu-greetings.png',
      types: [
        {
          description: 'Image file',
          accept: {
            'image/png': ['.png'],
          },
        },
      ],
    });
    const blob = await toBlob();
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

exportButton.style.display = 'block';
exportButton.addEventListener('click', async () => {
  await exportImage();
});

9. 🐟 Ajout de la compatibilité avec l'API Contact Picker

Vos utilisateurs peuvent souhaiter ajouter un message à leur carte de vœux et s'adresser personnellement à quelqu'un. Ajoutez une fonctionnalité qui permet à vos utilisateurs de sélectionner un ou plusieurs de leurs contacts locaux et d'ajouter leurs noms au message de partage.

Sur un appareil Android ou iOS, l'API Contact Picker vous permet de sélectionner des contacts dans l'application de gestion des contacts de l'appareil et de les renvoyer vers l'application. Modifiez le fichier contacts.mjs et ajoutez le code ci-dessous.

import { contactsButton, ctx, canvas } from './script.mjs';

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);
  }
};

contactsButton.style.display = 'block';
contactsButton.addEventListener('click', async () => {
  const contacts = await getContacts();
  if (contacts) {
    ctx.font = '1em Comic Sans MS';
    contacts.forEach((contact, index) => {
      ctx.fillText(contact.name.join(), 20, 16 * ++index, canvas.width);
    });
  }
});

10. 🐟 Ajouter la compatibilité avec l'API Async Clipboard

Vos utilisateurs peuvent vouloir coller une image provenant d'une autre application dans la vôtre, ou copier un dessin de votre application dans une autre. Ajoutez une fonctionnalité qui leur permet de copier et coller des images dans votre application et d'en sortir. L'API Async Clipboard est compatible avec les images PNG. Vous pouvez donc désormais lire et écrire des données d'image dans le presse-papiers.

Recherchez le fichier clipboard.mjs et ajoutez-y les éléments suivants :

import { copyButton, pasteButton, toBlob, drawImage } from './script.mjs';

const copy = async (blob) => {
  try {
    await navigator.clipboard.write([
      /* global ClipboardItem */
      new ClipboardItem({
        [blob.type]: blob,
      }),
    ]);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

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);
  }
};

copyButton.style.display = 'block';
copyButton.addEventListener('click', async () => {
  await copy(await toBlob());
});

pasteButton.style.display = 'block';
pasteButton.addEventListener('click', async () => {
  const image = new Image();
  image.addEventListener('load', () => {
    drawImage(image);
  });
  image.src = URL.createObjectURL(await paste());
});

11. 🐟 Ajouter la compatibilité avec l'API Badging

Lorsque vos utilisateurs installent votre application, une icône s'affiche sur leur écran d'accueil. Vous pouvez utiliser cette icône pour transmettre des informations amusantes, comme le nombre de coups de pinceau nécessaires pour un dessin donné.

Ajoutez une fonctionnalité qui incrémente le badge chaque fois que l'utilisateur effectue un nouveau coup de pinceau. L'API Badging permet de définir un badge numérique sur l'icône de l'application. Vous pouvez mettre à jour le badge chaque fois qu'un événement pointerdown se produit (c'est-à-dire lorsqu'un coup de pinceau est effectué) et le réinitialiser lorsque le canevas est effacé.

Placez le code ci-dessous dans le fichier badge.mjs :

import { canvas, clearButton } from './script.mjs';

let strokes = 0;

canvas.addEventListener('pointerdown', () => {
  navigator.setAppBadge(++strokes);
});

clearButton.addEventListener('click', () => {
  strokes = 0;
  navigator.setAppBadge(strokes);
});

12. 🐟 Ajouter la compatibilité avec l'API Screen Wake Lock

Parfois, vos utilisateurs peuvent avoir besoin de quelques instants pour simplement regarder un dessin, le temps que l'inspiration vienne. Ajoutez une fonctionnalité qui maintient l'écran allumé et empêche l'économiseur d'écran de se déclencher. L'API Screen Wake Lock empêche l'écran de l'utilisateur de se mettre en veille. Le verrouillage de l'activation est automatiquement libéré lorsqu'un événement de modification de la visibilité tel que défini par la visibilité de la page se produit. Par conséquent, le verrouillage de l'activation doit être réactivé lorsque la page redevient visible.

Recherchez le fichier wake_lock.mjs et ajoutez-y le contenu ci-dessous. Pour vérifier que cela fonctionne, configurez votre économiseur d'écran pour qu'il s'affiche au bout d'une minute.

import { wakeLockInput, wakeLockLabel } from './script.mjs';

let wakeLock = null;

const requestWakeLock = async () => {
  try {
    wakeLock = await navigator.wakeLock.request('screen');
    wakeLock.addEventListener('release', () => {
      console.log('Wake Lock was released');
    });
    console.log('Wake Lock is active');
  } catch (err) {
    console.error(err.name, err.message);
  }
};

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

document.addEventListener('visibilitychange', handleVisibilityChange);

wakeLockInput.style.display = 'block';
wakeLockLabel.style.display = 'block';
wakeLockInput.addEventListener('change', async () => {
  if (wakeLockInput.checked) {
    await requestWakeLock();
  } else {
    wakeLock.release();
  }
});

13. 🐟 Ajouter la prise en charge de l'API Periodic Background Sync

Commencer avec une zone de travail vierge peut être ennuyeux. Vous pouvez utiliser l'API Periodic Background Sync pour initialiser le canevas de vos utilisateurs avec une nouvelle image chaque jour, par exemple la photo quotidienne de fugu d'Unsplash.

Pour cela, vous avez besoin de deux fichiers : un fichier periodic_background_sync.mjs qui enregistre la synchronisation périodique en arrière-plan et un autre fichier image_of_the_day.mjs qui gère le téléchargement de l'image du jour.

Dans periodic_background_sync.mjs :

import { periodicBackgroundSyncButton, drawBlob } from './script.mjs';

const getPermission = async () => {
  const status = await navigator.permissions.query({
    name: 'periodic-background-sync',
  });
  return status.state === 'granted';
};

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);
  }
};

navigator.serviceWorker.addEventListener('message', async (event) => {
  const fakeURL = event.data.image;
  const mediaCache = await getMediaCache();
  const response = await mediaCache.match(fakeURL);
  drawBlob(await response.blob());
});

const getMediaCache = async () => {
  const keys = await caches.keys();
  return await caches.open(keys.filter((key) => key.startsWith('media'))[0]);
};

periodicBackgroundSyncButton.style.display = 'block';
periodicBackgroundSyncButton.addEventListener('click', async () => {
  if (await getPermission()) {
    await registerPeriodicBackgroundSync();
  }
  const mediaCache = await getMediaCache();
  let blob = await mediaCache.match('./assets/background.jpg');
  if (!blob) {
    blob = await mediaCache.match('./assets/fugu_greeting_card.jpg');
  }
  drawBlob(await blob.blob());
});

Dans image_of_the_day.mjs :

const getImageOfTheDay = async () => {
  try {
    const fishes = ['blowfish', 'pufferfish', 'fugu'];
    const fish = fishes[Math.floor(fishes.length * Math.random())];
    const response = await fetch(`https://source.unsplash.com/daily?${fish}`);
    if (!response.ok) {
      throw new Error('Response was', response.status, response.statusText);
    }
    return await response.blob();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

const getMediaCache = async () => {
  const keys = await caches.keys();
  return await caches.open(keys.filter((key) => key.startsWith('media'))[0]);
};

self.addEventListener('periodicsync', (syncEvent) => {
  if (syncEvent.tag === 'image-of-the-day-sync') {
    syncEvent.waitUntil(
      (async () => {
        try {
          const blob = await getImageOfTheDay();
          const mediaCache = await getMediaCache();
          const fakeURL = './assets/background.jpg';
          await mediaCache.put(fakeURL, new Response(blob));
          const clients = await self.clients.matchAll();
          clients.forEach((client) => {
            client.postMessage({
              image: fakeURL,
            });
          });
        } catch (err) {
          console.error(err.name, err.message);
        }
      })(),
    );
  }
});

14. 🐟 Ajouter la prise en charge de l'API Shape Detection

Parfois, les dessins de vos utilisateurs ou les images d'arrière-plan utilisées peuvent contenir des informations utiles, comme des codes-barres. L'API Shape Detection, et plus précisément l'API Barcode Detection, vous permet d'extraire ces informations. Ajoutez une fonctionnalité qui tente de détecter les codes-barres à partir des dessins de vos utilisateurs. Localisez le fichier barcode.mjs et ajoutez-y le contenu ci-dessous. Pour tester cette fonctionnalité, il vous suffit de charger ou de coller une image avec un code-barres sur le canevas. Vous pouvez copier un exemple de code-barres à partir d'une recherche d'images de codes QR.

/* global BarcodeDetector */
import {
  scanButton,
  clearButton,
  canvas,
  ctx,
  CANVAS_BACKGROUND,
  CANVAS_COLOR,
  floor,
} from './script.mjs';

const barcodeDetector = new BarcodeDetector();

const detectBarcodes = async (canvas) => {
  return await barcodeDetector.detect(canvas);
};

scanButton.style.display = 'block';
let seenBarcodes = [];
clearButton.addEventListener('click', () => {
  seenBarcodes = [];
});
scanButton.addEventListener('click', async () => {
  const barcodes = await detectBarcodes(canvas);
  if (barcodes.length) {
    barcodes.forEach((barcode) => {
      const rawValue = barcode.rawValue;
      if (seenBarcodes.includes(rawValue)) {
        return;
      }
      seenBarcodes.push(rawValue);
      ctx.font = '1em Comic Sans MS';
      ctx.textAlign = 'center';
      ctx.fillStyle = CANVAS_BACKGROUND;
      const boundingBox = barcode.boundingBox;
      const left = boundingBox.left;
      const top = boundingBox.top;
      const height = boundingBox.height;
      const oneThirdHeight = floor(height / 3);
      const width = boundingBox.width;
      ctx.fillRect(left, top + oneThirdHeight, width, oneThirdHeight);
      ctx.fillStyle = CANVAS_COLOR;
      ctx.fillText(
        rawValue,
        left + floor(width / 2),
        top + floor(height / 2),
        width,
      );
    });
  }
});

15. 🐡 Ajouter la compatibilité avec l'API Idle Detection

Si vous imaginez que votre application s'exécute dans une configuration de type borne, une fonctionnalité utile serait de réinitialiser le canevas après une certaine période d'inactivité. L'API Idle Detection vous permet de détecter quand un utilisateur n'interagit plus avec son appareil.

Recherchez le fichier idle_detection.mjs et collez-y le contenu ci-dessous.

import { ephemeralInput, ephemeralLabel, clearCanvas } from './script.mjs';

let controller;

ephemeralInput.style.display = 'block';
ephemeralLabel.style.display = 'block';

ephemeralInput.addEventListener('change', async () => {
  if (ephemeralInput.checked) {
    const state = await IdleDetector.requestPermission();
    if (state !== 'granted') {
      ephemeralInput.checked = false;
      return alert('Idle detection permission must be granted!');
    }
    try {
      controller = new AbortController();
      const idleDetector = new IdleDetector();
      idleDetector.addEventListener('change', (e) => {
        const { userState, screenState } = e.target;
        console.log(`idle change: ${userState}, ${screenState}`);
        if (userState === 'idle') {
          clearCanvas();
        }
      });
      idleDetector.start({
        threshold: 60000,
        signal: controller.signal,
      });
    } catch (err) {
      console.error(err.name, err.message);
    }
  } else {
    console.log('Idle detection stopped.');
    controller.abort();
  }
});

16. 🐡 Ajouter la compatibilité avec l'API File Handling

Et si vos utilisateurs pouvaient simplement double-cliquer sur un fichier image pour que votre application s'affiche ? L'API File Handling vous permet de le faire.

Vous devrez enregistrer la PWA en tant que gestionnaire de fichiers pour les images. Cela se produit dans le fichier manifeste de l'application Web. L'extrait ci-dessous du fichier manifest.webmanifest le montre. (Cette ligne fait déjà partie du fichier manifeste. Vous n'avez pas besoin de l'ajouter vous-même.)

{
  "file_handlers": [
    {
      "action": "./",
      "accept": {
        "image/*": [".jpg", ".jpeg", ".png", ".webp", ".svg"]
      }
    }
  ]
}

Pour gérer réellement les fichiers ouverts, ajoutez le code ci-dessous au fichier file-handling.mjs :

import { drawBlob } from './script.mjs';

const handleLaunchFiles = () => {
  window.launchQueue.setConsumer((launchParams) => {
    if (!launchParams.files.length) {
      return;
    }
    launchParams.files.forEach(async (handle) => {
      const file = await handle.getFile();
      drawBlob(file);
    });
  });
};

handleLaunchFiles();

17. Félicitations

🎉 Félicitations, vous avez tout visionné !

De nombreuses API de navigateur intéressantes sont en cours de développement dans le cadre du projet Fugu 🐡. Cet atelier de programmation ne fait qu'effleurer le sujet.

Pour en savoir plus, consultez nos publications sur notre site web.dev.

Page de destination de la section &quot;Fonctionnalités&quot; du site web.dev.

Mais ce n'est pas tout. Pour les mises à jour qui n'ont pas encore été publiées, vous pouvez accéder à notre outil de suivi des API Fugu. Il contient des liens vers toutes les propositions qui ont été déployées, qui sont en version d'évaluation de l'origine ou en version d'évaluation pour les développeurs, toutes les propositions sur lesquelles le travail a commencé et tout ce qui est envisagé, mais pas encore commencé.

Site Web de suivi de l&#39;API Fugu

Cet atelier de programmation a été écrit par Thomas Steiner (@tomayac). Je me ferai un plaisir de répondre à vos questions et de lire vos commentaires. Un grand merci à Hemanth H.M (@GNUmanth), Christian Liebel (@christianliebel), Sven May (@Svenmay), Lars Knudsen (@larsgk) et Jackie Han (@hanguokai) qui ont contribué à la création de cet atelier de programmation !