Neue und zukünftige Browserfunktionen für Ihre PWA: Von Fugu With Love

1. Hinweis

Progressive Web-Anwendungen (PWAs) sind Anwendungssoftware, die über das Web bereitgestellt wird und mit gängigen Webtechnologien wie HTML, CSS und JavaScript erstellt wurde. Sie sind für alle Plattformen vorgesehen, die einen standardkonformen Browser verwenden.

In diesem Codelab starten Sie mit einer Basis-PWA und entdecken dann neue Browserfunktionen, mit denen Sie Ihre PWA optimal nutzen können 🦸.

Viele dieser Funktionen sind noch in Betrieb, sie werden also noch standardisiert. Manchmal müssen Sie Browser-Flags festlegen, um sie verwenden zu können.

Vorbereitung

Für dieses Codelab sollten Sie mit modernem JavaScript vertraut sein, insbesondere mit Promise/Await. Nicht alle Schritte des Codelabs werden auf allen Plattformen unterstützt. Es kann hilfreich sein, zusätzliche Geräte zu testen, zum Beispiel ein Android-Smartphone oder einen Laptop, auf dem ein anderes Betriebssystem verwendet wird als das Gerät, auf dem Sie den Code bearbeiten. Als Alternative zu echten Geräten können Sie Simulatoren wie den Android-Simulator oder Onlinedienste wie BrowserStack verwenden, mit denen Sie Tests auf Ihrem aktuellen Gerät durchführen können. Ansonsten kannst du auch einfach einen Schritt überspringen.

Aufgaben

Sie erstellen eine Web-App mit Grußkarten und erfahren, wie neue und künftige Browserfunktionen Ihre App verbessern können, sodass sie in bestimmten Browsern optimal funktioniert, jedoch in allen modernen Browsern nützlich bleibt.

Hier erfahren Sie, wie Sie Supportfunktionen hinzufügen, z. B. Dateizugriff, Zugriff auf die Zwischenablage, Abruf von Kontakten, regelmäßige Synchronisierung im Hintergrund, Bildschirm-Wakelock, Freigabefunktionen und mehr.

Wenn Sie das Codelab durchgearbeitet haben, werden Sie wissen, wie Sie Ihre Web-Apps mit neuen Browserfunktionen schrittweise optimieren können. Sie machen sich nicht mit einer Belastung Ihrer Teilmenge von Nutzern vertraut, die in nicht kompatiblen Browsern auftreten, und, was noch wichtiger ist, Sie können diese aus der App ausschließen.

Voraussetzungen

Derzeit werden folgende Browser vollständig unterstützt:

Es empfiehlt sich, die jeweilige Entwicklerversion zu verwenden.

2. Projekt Fugu

Progressive Web-Apps (PWAs) sind mit modernen APIs ausgestattet und optimiert, um erweiterte Funktionen, Zuverlässigkeit und Installierbarkeit zu bieten und gleichzeitig alle Nutzer im Web und auf allen Geräten zu erreichen.

Einige dieser APIs sind besonders leistungsstark. Bei unsachgemäßer Handhabung kann es zu Problemen kommen. Genau wie der Fugu-Fisch 🐡: Wenn man richtig schneidet, ist das eine Delikatessen. Aber wenn man sie falsch schneidet, kann es tödlich sein. Aber keine Sorge, in diesem Codelab kann nichts kaputtgehen.

Aus diesem Grund ist der interne Codename des Web Skills-Projekts, in dem die beteiligten Unternehmen diese neuen APIs entwickeln, Projekt Fugu.

Webfunktionen schon heute ermöglichen großen und kleinen Unternehmen die Nutzung reiner browserbasierter Lösungen, die oft eine schnellere Bereitstellung mit niedrigeren Entwicklungskosten ermöglichen als eine plattformspezifische Route.

3. Erste Schritte

Laden Sie einen der beiden Browser herunter und legen Sie dann das folgende Laufzeit-Flag 🚩 fest, indem Sie about://flags aufrufen. Diese funktioniert sowohl in Chrome als auch in Edge:

  • #enable-experimental-web-platform-features

Starten Sie den Browser anschließend neu.

Verwenden Sie die Plattform Glitch, weil Sie dort eine PWA hosten können, weil sie einen guten Editor hat. Glitch unterstützt auch den Import und Export in GitHub. Anbieter sind also nicht an einen Anbieter gebunden. Gehen Sie zu fugu- Paint.glitch.me, um die Anwendung auszuprobieren. Es ist eine einfache Zeichen-App 🎨, das du im Codelab verbessern möchtest.

PWA mit Baseline zur Begrüßung mit Fugu

Nachdem Sie die App gespielt haben, erstellen Sie einen eigenen Mix für die App, um eine eigene Kopie zu erstellen, die Sie bearbeiten können. Die URL Ihres Remixes sieht ungefähr so aus: glitch.com/editaggregate/bouncy-candytuft. Dieser Remix ist weltweit verfügbar. Melde dich mit einem bestehenden Konto an oder erstelle ein neues Konto in Glitch, um deine Änderungen zu speichern. Sie können sich Ihre App ansehen, indem Sie auf die Schaltfläche „&zeigen“ klicken und die URL der gehosteten App in etwa bouncy-candytuft.glitch.me ist. Achten Sie darauf, dass .me statt .com als Top-Level-Domain ist.

Jetzt kannst du deine App bearbeiten und verbessern. Wenn Sie Änderungen vornehmen, wird die App neu geladen und Ihre Änderungen sind sichtbar.

Die Glitch-IDE zeigt die Bearbeitung eines HTML-Dokuments.

Die folgenden Aufgaben sollten idealerweise in der angegebenen Reihenfolge erledigt werden. Aber wie oben erwähnt, können Sie einen Schritt immer überspringen, wenn Sie keinen Zugriff auf ein kompatibles Gerät haben. Denken Sie daran, dass jede Aufgabe entweder mit 🐟, einem harmlosen Süßwasserfisch oder 🐡, einem Handle mit Sorgfalt und Fugu gekennzeichnet ist, um Sie darüber zu informieren, wie sehr diese Funktion experimentell ist oder nicht.

Sehen Sie in der Console in den Entwicklertools nach, ob eine API auf dem aktuellen Gerät unterstützt wird. Außerdem nutzen wir Glitch, damit du dieselbe App ganz einfach auf verschiedenen Geräten aufrufen kannst, beispielsweise auf einem Smartphone oder einem Computer.

API-Kompatibilität, die in den Entwicklertools in der Konsole protokolliert wird.

4. 🐟 Web Share API-Unterstützung hinzufügen

Es ist langweilig, die tollsten Zeichnungen zu erschaffen, da niemand da ist, sie zu schätzen. Fügen Sie eine Funktion hinzu, mit der Nutzer ihre Zeichnungen in Form von Grußkarten mit der Welt teilen können.

Die Web Share API unterstützt die Freigabe von Dateien. Wie Sie sich vielleicht erinnern, ist File nur eine spezifische Art von Blob. Importieren Sie daher in der Datei „share.mjs“ die Schaltfläche „Teilen“ und eine Aktionsfunktion toBlob(), die den Inhalt eines Canvas-Elements in ein Blob konvertiert, und fügen Sie die Freigabefunktion gemäß dem Code unten hinzu.

Wenn dies der Fall ist, die Schaltfläche aber nicht angezeigt wird, liegt dies daran, dass der Browser die Web Share API nicht implementiert.

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. 🐟 Web Share Target API-Unterstützung hinzufügen

Ihre Nutzer können jetzt Grußkarten verwenden, die über die App erstellt wurden. Sie können Nutzern aber auch erlauben, Bilder mit Ihrer App zu teilen und sie in Grußkarten umzuwandeln. Sie können dazu die Web Share Target API verwenden.

Im Webanwendungs-Manifest müssen Sie der App mitteilen, welche Art von Dateien Sie akzeptieren und welche URL der Browser aufrufen soll, wenn eine oder mehrere Dateien freigegeben werden. Der Auszug unter der Datei „manifest.webmanifest“ zeigt diesen Wert.

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

Der Service Worker verarbeitet die empfangenen Dateien. Die URL ./share-target/ existiert nicht. Die App führt diese im fetch-Handler aus und leitet die Anfrage durch Hinzufügen des Suchparameters ?share-target an die Stamm-URL weiter:

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 */

  /* ... */
});

Nach dem Laden der App wird geprüft, ob der Suchparameter festgelegt ist. Ist dies der Fall, wird das freigegebene Bild auf dem Canvas gezeichnet und aus dem Cache gelöscht. Dies geschieht in 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');
  }
};

Diese Funktion wird dann verwendet, wenn die App initialisiert wird.

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

6. 🐟 Unterstützung beim Import von Bildern

Es ist nicht einfach, alles neu zu zeichnen. Fügen Sie eine Funktion hinzu, mit der Nutzer ein lokales Bild von ihrem Gerät in die App hochladen können.

Lesen Sie zuerst die Informationen zur Funktion drawImage(). Als Nächstes machen Sie sich mit dem Element <​input
type=file>
vertraut.

Anhand dieser Informationen können Sie die Datei mit dem Namen import_image_legacy.mjs bearbeiten und das folgende Snippet hinzufügen. Am oberen Rand der Datei importieren Sie die Schaltfläche „Importieren“ und eine praktische Funktion drawBlob(), mit der Sie ein Blob auf den Canvas zeichnen können.

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. 🐟 Support für den Export von Bildern

Wie speichert Ihr Nutzer eine in der App erstellte Datei auf seinem Gerät? Traditionell wurde das mit einem <​a
download>
-Element erreicht.

Füge in der Datei export_image_legacy.mjs die Inhalte wie unten beschrieben hinzu. Importieren Sie die Schaltfläche „Exportieren“ und eine toBlob()-Funktion, die den Canvas-Inhalt in ein Blob konvertiert.

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. 🐟 Unterstützung für File System Access API hinzufügen

Dabei geht es am besten um die Freigabe, aber Ihre Nutzer möchten ihre Arbeit vielleicht auch auf ihren eigenen Geräten speichern. Eine Funktion hinzufügen, mit der Nutzer ihre Zeichnungen speichern und wieder öffnen können

Bisher hast du beim Importieren von Dateien die alte Methode <​input type=file> und beim Export von Dateien die alte Methode <​a download> verwendet. Verwenden Sie nun die File System Access API, um das Arbeiten zu optimieren.

Diese API ermöglicht das Öffnen und Speichern von Dateien aus dem Dateisystem des Betriebssystems. Bearbeiten Sie die beiden Dateien import_image.mjs und export_image.mjs, indem Sie den Inhalt unten hinzufügen. Entferne die 🐡-Emojis aus script.mjs, damit diese Dateien geladen werden können.

Ersetzen Sie diese Zeile:

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

...mit dieser Zeile:

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

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

In 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. 🐟 Kontaktauswahl-API-Support hinzufügen

Nutzer können eine Grußkarte hinzufügen und eine Person persönlich ansprechen. Fügen Sie eine Funktion hinzu, mit der Nutzer einen oder mehrere ihrer lokalen Kontakte auswählen und ihre Namen zur Freigabenachricht hinzufügen können.

Auf einem Android- oder iOS-Gerät können Sie über die Contact Picker API Kontakte aus der Kontakte-Manager-App auf dem Gerät auswählen und an die App zurückgeben. Bearbeite die Datei contacts.mjs und füge den Code unten hinzu.

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. 🐟 Async-API-Unterstützung für Clipboards hinzufügen

Nutzer können Bilder aus einer anderen App in Apps einbinden oder Zeichnungen aus Ihrer App in eine andere App kopieren. Außerdem haben Sie die Möglichkeit, Bilder einzufügen und zu kopieren und in die App zu kopieren. Mit der Async Clipboard API können Sie jetzt Bilddaten aus der Zwischenablage abrufen und in die Zwischenablage schreiben.

Suchen Sie die Datei clipboard.mjs und fügen Sie Folgendes hinzu:

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. 🐟 Unterstützung für die Badge API hinzufügen

Wenn Nutzer Ihre App installieren, wird auf dem Startbildschirm ein Symbol angezeigt. Über dieses Symbol können Sie lustige Informationen vermitteln, z. B. die Anzahl der Pinselstriche für eine bestimmte Zeichnung.

Fügen Sie eine Funktion hinzu, die jedes Mal das Abzeichen gezählt wird, wenn ein Nutzer einen neuen Pinselstrich macht. Über die Badge API können Sie ein numerisches Logo für das App-Symbol festlegen. Sie können das Logo jederzeit aktualisieren, wenn ein pointerdown-Ereignis stattfindet, d. h., wenn ein Pinselstrich erfolgt. Außerdem lässt sich das Logo nach dem Löschen des Canvas zurücksetzen.

Füge den folgenden Code in die Datei badge.mjs ein:

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

let strokes = 0;

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

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

12. 🐟 Bildschirm-Wakelock API-Unterstützung hinzufügen

Manchmal brauchen Ihre Nutzer einen Moment, um eine Zeichnung anzustarren. Fügen Sie eine Funktion hinzu, die dafür sorgt, dass der Bildschirm wach ist und der Bildschirmschoner erscheint. Die Screen Wakelock API verhindert, dass der Bildschirm des Nutzers einschläft. Die Wakelock-Sperre wird automatisch aufgehoben, wenn eine Änderung der Sichtbarkeit ausgelöst wird, wie in der Seitensichtbarkeit definiert. Daher muss die Wakelock-Funktion wieder angewendet werden, wenn die Seite wieder sichtbar ist.

Suche die Datei wake_lock.mjs und füge den unten stehenden Inhalt hinzu. Konfigurieren Sie den Bildschirmschoner, der nach einer Minute angezeigt werden soll, um zu sehen, ob das funktioniert.

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. 🐟 Regelmäßige Background API-Unterstützung hinzufügen

Eine leere Canvas-Anzeige kann langweilig sein. Und mit der Periodic Background Sync API kannst du jeden Tag ein neues Canvas-Element mit dem neuen Canvas erstellen, z. B. das tägliche Fugu-Foto von Unsplash.

Dazu sind zwei Dateien erforderlich: eine Datei periodic_background_sync.mjs, mit der die regelmäßige Hintergrundsynchronisierung registriert wird, und eine weitere Datei image_of_the_day.mjs, in der das Herunterladen des Bildes des Tages beschrieben wird.

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

In 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. 🐟 Hinzufügen der Formerkennungs-API-Unterstützung

Manchmal enthalten Ihre Zeichnungen oder die verwendeten Hintergrundbilder nützliche Informationen wie z. B. Barcodes. Mit der Shape Detection API, insbesondere der Barcode Detection API, können Sie diese Informationen extrahieren. Funktion hinzufügen, mit der Barcodes Ihrer Nutzer erkannt werden sollen Suchen Sie nach der Datei barcode.mjs und fügen Sie den Inhalt unten hinzu. Zum Testen dieser Funktion laden Sie einfach ein Bild mit einem Barcode auf den Canvas oder fügen ihn ein. Sie können einen Beispiel-Barcode aus einer Bildersuche für QR-Codes kopieren.

/* 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. 🐡 Support-ID bei Inaktivitätserkennung hinzufügen

Wenn Sie davon ausgehen, dass Ihre App in einer kioskähnlichen Einrichtung ausgeführt wird, wäre es sinnvoll, den Canvas nach einer bestimmten Zeitspanne zurückzusetzen. Mit der Idle Detection API können Sie erkennen, wann ein Nutzer nicht mehr mit seinem Gerät interagiert.

Fügen Sie die Datei idle_detection.mjs ein und fügen Sie den Inhalt unten ein.

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. 🐡 Unterstützung der File Handling API

Was wäre, wenn Nutzer einfach eine Bilddatei mit einer Doppelklick Ihrer App verlinken würden und Ihre App dann angezeigt wird? Mit der File Handling API ist genau das möglich.

Sie müssen die PWA als Datei-Handler für Bilder registrieren. Dies geschieht im Manifest der Web-App, im Auszug unter der Datei manifest.webmanifest. Dies ist bereits Teil des Manifests. Sie müssen es nicht selbst hinzufügen.

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

Fügen Sie der Datei file-handling.mjs folgenden Code hinzu, damit die geöffneten Dateien verarbeitet werden:

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. Glückwunsch

🎉 Wow, du hast es geschafft!

Im Rahmen des Projekts Fugu 🐡 gibt es so viele aufregende Browser-APIs, dass das Codelab die Oberfläche kaum überdecken könnte.

Weitere Informationen oder weitere Informationen findest du auf unserer Website web.dev.

Landingpage des Bereichs „“Funktionen&ampquo;rdquo“ der Website „web.dev“.

Aber das ist noch nicht alles. Für Updates, die noch nicht veröffentlicht wurden, können Sie auf unseren Fugu API-Tracker zugreifen. Er enthält Links zu allen Angeboten, die versendet wurden, sich im Ursprungs- oder Entwicklertest befinden, alle Angebote, für die die Arbeit begonnen hat, und alle Dinge, die berücksichtigt, aber noch nicht begonnen haben.

Fugu API-Tracker-Website

Dieses Codelab wurde von Thomas Steiner (@tomayac) verfasst. Ich freue mich, Ihre Fragen zu beantworten. Wir möchten uns bei Hemanth H.M (@GNUmanth), Christian Liebel (@christianliebel), Sven May (@Svenmay), Lars Knudsen (@larsgk) und Jackie Han (@hanguokai) bedanken, die an diesem Codelab beteiligt waren.