Poznaj nowe i nadchodzące funkcje przeglądarki dla Twojej progresywnej aplikacji internetowej: From Fugu With Love

1. Zanim zaczniesz

Progresywne aplikacje internetowe (PWA) to rodzaj oprogramowania dostarczanego przez internet, które jest tworzone przy użyciu popularnych technologii internetowych, takich jak HTML, CSS i JavaScript. Są one przeznaczone do działania na każdej platformie, która korzysta z przeglądarki zgodnej ze standardami.

W tym laboratorium kodów zaczniesz od podstawowej aplikacji PWA, a następnie poznasz nowe możliwości przeglądarki, które ostatecznie dadzą Twojej aplikacji PWA supermoce 🦸.

Wiele z tych nowych funkcji przeglądarki jest w trakcie wdrażania i standaryzacji, więc czasami trzeba będzie ustawić flagi przeglądarki, aby z nich korzystać.

Wymagania wstępne

Aby ukończyć to ćwiczenie, musisz znać nowoczesny JavaScript, a w szczególności obietnice i funkcje asynchroniczne. Nie wszystkie kroki codelabu są obsługiwane na wszystkich platformach, dlatego podczas testowania warto mieć pod ręką dodatkowe urządzenia, np. telefon z Androidem lub laptop z innym systemem operacyjnym niż urządzenie, na którym edytujesz kod. Zamiast prawdziwych urządzeń możesz używać symulatorów, np. symulatora Androida, lub usług online, takich jak BrowserStack, które umożliwiają testowanie na obecnym urządzeniu. Możesz też pominąć dowolny krok, ponieważ nie są one od siebie zależne.

Co utworzysz

Stworzysz aplikację internetową do tworzenia kartek z życzeniami i dowiesz się, jak nowe i przyszłe funkcje przeglądarek mogą ulepszyć Twoją aplikację, aby zapewniała zaawansowane funkcje w niektórych przeglądarkach (ale pozostawała przydatna we wszystkich nowoczesnych przeglądarkach).

Dowiesz się, jak dodać funkcje pomocnicze, takie jak dostęp do systemu plików, dostęp do systemowego schowka, pobieranie kontaktów, okresowa synchronizacja w tle, blokada wybudzania ekranu, funkcje udostępniania i inne.

Po ukończeniu tego kursu zdobędziesz solidną wiedzę o tym, jak stopniowo ulepszać aplikacje internetowe za pomocą nowych funkcji przeglądarki, nie obciążając przy tym pobieraniem tych użytkowników, którzy korzystają z niekompatybilnych przeglądarek, a co najważniejsze, nie wykluczając ich z aplikacji.

Czego potrzebujesz

Obecnie w pełni obsługiwane przeglądarki to:

  • Chrome
  • i opartej na Chromium przeglądarce Edge.

Zalecamy korzystanie z określonego kanału deweloperskiego.

2. Project Fugu

Progresywne aplikacje internetowe (PWA) są tworzone i ulepszane za pomocą nowoczesnych interfejsów API, aby zapewniać większe możliwości, niezawodność i łatwość instalacji, a jednocześnie docierać do każdego użytkownika w dowolnym miejscu na świecie i na dowolnym urządzeniu.

Niektóre z tych interfejsów API są bardzo zaawansowane i w przypadku nieprawidłowego użycia mogą powodować problemy. Podobnie jak w przypadku ryby fugu 🐡: jeśli ją dobrze pokroisz, będzie przysmakiem, ale jeśli źle, może być śmiertelna (ale nie martw się, w tym laboratorium nic się nie zepsuje).

Dlatego wewnętrzna nazwa kodowa projektu Web Capabilities (w ramach którego zaangażowane firmy opracowują te nowe interfejsy API) to Project Fugu.

Możliwości internetowe już dziś pozwalają dużym i małym przedsiębiorstwom tworzyć rozwiązania oparte wyłącznie na przeglądarce, co często umożliwia szybsze wdrażanie przy niższych kosztach rozwoju w porównaniu z rozwiązaniami przeznaczonymi na konkretne platformy.

3. Rozpocznij

Pobierz dowolną z tych przeglądarek, a potem ustaw flagę czasu działania 🚩, przechodząc do about://flags. Działa ona zarówno w Chrome, jak i w Edge:

  • #enable-experimental-web-platform-features

Po włączeniu tej funkcji uruchom ponownie przeglądarkę.

Będziesz korzystać z platformy Glitch, ponieważ umożliwia ona hostowanie progresywnych aplikacji internetowych i ma przyzwoity edytor. Glitch obsługuje też importowanie i eksportowanie do GitHuba, więc nie ma ryzyka uzależnienia od dostawcy. Otwórz stronę fugu-paint.glitch.me, aby wypróbować aplikację. Jest to podstawowa aplikacja do rysowania 🎨, którą ulepszysz w trakcie tego laboratorium.

Podstawowa progresywna aplikacja internetowa Fugu Greetings z dużym płótnem, na którym namalowano słowo „Google”.

Po wypróbowaniu aplikacji możesz ją zremiksować, aby utworzyć własną kopię, którą możesz edytować. Adres URL Twojego remiksu będzie wyglądać mniej więcej tak: glitch.com/edit/#!/bouncy-candytuft (w Twoim przypadku „bouncy-candytuft” będzie inne). Ten remix jest bezpośrednio dostępny na całym świecie. Aby zapisać swoją pracę, zaloguj się na istniejące konto lub utwórz nowe na platformie Glitch. Aby zobaczyć aplikację, kliknij przycisk „🕶 Pokaż”. Adres URL hostowanej aplikacji będzie wyglądać np. tak: bouncy-candytuft.glitch.me (zwróć uwagę na .me zamiast .com jako domenę najwyższego poziomu).

Teraz możesz edytować aplikację i ją ulepszać. Za każdym razem, gdy wprowadzisz zmiany, aplikacja zostanie ponownie załadowana i będą one od razu widoczne.

IDE Glitch pokazujące edycję dokumentu HTML.

Poniższe zadania należy wykonać w podanej kolejności, ale jak wspomnieliśmy powyżej, zawsze możesz pominąć krok, jeśli nie masz dostępu do zgodnego urządzenia. Pamiętaj, że każde zadanie jest oznaczone symbolem 🐟 (nieszkodliwa ryba słodkowodna) lub 🐡 (ryba fugu, z którą należy się obchodzić ostrożnie). Informuje to o tym, czy dana funkcja jest eksperymentalna.

Sprawdź konsolę w narzędziach deweloperskich, aby dowiedzieć się, czy interfejs API jest obsługiwany na danym urządzeniu. Korzystamy też z Glitch, aby umożliwić Ci łatwe sprawdzanie tej samej aplikacji na różnych urządzeniach, np. na telefonie komórkowym i komputerze.

Zgodność interfejsu API jest rejestrowana w konsoli w Narzędziach deweloperskich.

4. 🐟 Dodawanie obsługi interfejsu Web Share API

Tworzenie najwspanialszych rysunków jest nudne, jeśli nie ma nikogo, kto mógłby je docenić. Dodaj funkcję, która umożliwi użytkownikom udostępnianie swoich rysunków w formie kartek z życzeniami.

Web Share API obsługuje udostępnianie plików, a jak być może pamiętasz, File to tylko określony rodzaj Blob. Dlatego w pliku o nazwie share.mjs zaimportuj przycisk udostępniania i funkcję pomocniczą toBlob(), która konwertuje zawartość elementu canvas na obiekt blob, a następnie dodaj funkcję udostępniania zgodnie z poniższym kodem.

Jeśli masz zaimplementowaną tę funkcję, ale nie widzisz przycisku, oznacza to, że Twoja przeglądarka nie obsługuje interfejsu Web Share API.

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. 🐟 Dodaj obsługę interfejsu Web Share Target API

Użytkownicy mogą teraz udostępniać kartki z życzeniami utworzone za pomocą aplikacji, ale możesz też zezwolić im na udostępnianie zdjęć w aplikacji i przekształcanie ich w kartki z życzeniami. W tym celu możesz użyć interfejsu Web Share Target API.

W pliku manifestu aplikacji internetowej musisz określić, jakie typy plików możesz akceptować i jaki adres URL przeglądarka powinna wywołać, gdy udostępnionych zostanie co najmniej 1 plik. Pokazuje to poniższy fragment pliku manifest.webmanifest.

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

Następnie service worker przetwarza otrzymane pliki. Adres URL ./share-target/ w rzeczywistości nie istnieje. Aplikacja po prostu reaguje na niego w procedurze obsługi fetch i przekierowuje żądanie do głównego adresu URL, dodając parametr zapytania ?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 */

  /* ... */
});

Po wczytaniu aplikacji sprawdza ona, czy ten parametr zapytania jest ustawiony. Jeśli tak, rysuje udostępniony obraz na obszarze roboczym i usuwa go z pamięci podręcznej. Wszystko to dzieje się w domenie 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');
  }
};

Ta funkcja jest następnie używana podczas inicjowania aplikacji.

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

6. 🐟 Dodawanie obsługi importowania obrazów

Rysowanie wszystkiego od zera jest trudne. Dodaj funkcję, która umożliwi użytkownikom przesyłanie do aplikacji lokalnych obrazów z urządzeń.

Najpierw zapoznaj się z funkcją drawImage() obszaru roboczego. Następnie zapoznaj się z elementem <​input
type=file>
.

Mając tę wiedzę, możesz edytować plik o nazwie import_image_legacy.mjs i dodać ten fragment kodu. U góry pliku, który importujesz, znajduje się przycisk importowania i funkcja pomocnicza drawBlob(), która umożliwia narysowanie obiektu blob na kanwie.

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. 🐟 Dodawanie obsługi eksportowania obrazów

W jaki sposób użytkownik zapisze plik utworzony w aplikacji na urządzeniu? Tradycyjnie osiąga się to za pomocą elementu <​a
download>
.

W pliku export_image_legacy.mjs dodaj treść w sposób opisany poniżej. Zaimportuj przycisk eksportowania i funkcję pomocniczą toBlob(), która konwertuje zawartość obszaru rysowania na obiekt 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. 🐟 Dodanie obsługi interfejsu File System Access API

Udostępnianie jest ważne, ale użytkownicy prawdopodobnie będą chcieli zapisywać swoje najlepsze prace na własnych urządzeniach. Dodaj funkcję, która umożliwi użytkownikom zapisywanie (i ponowne otwieranie) rysunków.

Wcześniej do importowania plików używano <​input type=file>starszego podejścia<​a download>, a do eksportowania plików – <​a download>starszego podejścia<​a download>. Teraz użyjesz interfejsu File System Access API, aby zwiększyć wygodę użytkowników.

Ten interfejs API umożliwia otwieranie i zapisywanie plików z systemu plików systemu operacyjnego. Edytuj pliki import_image.mjsexport_image.mjs, dodając do nich poniższą zawartość. Aby te pliki się wczytały, usuń emoji 🐡 z script.mjs.

Zastąp ten wiersz:

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

...z tym wierszem:

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

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

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. 🐟 Dodanie obsługi interfejsu Contact Picker API

Użytkownicy mogą chcieć dodać wiadomość do kartki z życzeniami i zwrócić się do kogoś osobiście. Dodaj funkcję, która pozwala użytkownikom wybrać jeden lub więcej kontaktów lokalnych i dodać ich nazwy do wiadomości udostępniania.

Na urządzeniach z Androidem i iOS interfejs Contact Picker API umożliwia wybieranie kontaktów z aplikacji do zarządzania kontaktami na urządzeniu i przekazywanie ich do aplikacji. Edytuj plik contacts.mjs i dodaj poniższy kod.

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. 🐟 Dodawanie obsługi interfejsu Async Clipboard API

Użytkownicy mogą chcieć wkleić obraz z innej aplikacji do Twojej aplikacji lub skopiować rysunek z Twojej aplikacji do innej aplikacji. Dodaj funkcję, która umożliwia kopiowanie i wklejanie obrazów do i z Twojej aplikacji. Interfejs Async Clipboard API obsługuje obrazy PNG, więc możesz teraz odczytywać i zapisywać dane obrazu w schowku.

Znajdź plik clipboard.mjs i dodaj te informacje:

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. 🐟 Dodawanie obsługi interfejsu Badging API

Gdy użytkownicy zainstalują Twoją aplikację, na ich ekranie głównym pojawi się ikona. Możesz użyć tej ikony, aby przekazać ciekawe informacje, np. liczbę pociągnięć pędzlem w danym rysunku.

Dodaj funkcję, która zlicza pociągnięcia pędzlem użytkownika. Interfejs Badging API umożliwia ustawienie na ikonie aplikacji plakietki z liczbą. Możesz aktualizować plakietkę za każdym razem, gdy nastąpi pointerdownzdarzenie (czyli gdy pojawi się pociągnięcie pędzlem), i resetować ją, gdy obszar roboczy zostanie wyczyszczony.

Wklej poniższy kod do pliku 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. 🐟 Dodanie obsługi interfejsu Screen Wake Lock API

Czasami użytkownicy mogą potrzebować chwili, aby po prostu wpatrywać się w rysunek i zaczerpnąć inspiracji. Dodaj funkcję, która utrzymuje ekran włączony i zapobiega włączeniu wygaszacza ekranu. Interfejs Screen Wake Lock API zapobiega przejściu ekranu użytkownika w tryb uśpienia. Blokada wybudzania jest zwalniana automatycznie, gdy wystąpi zdarzenie zmiany widoczności zdefiniowane przez Page Visibility. Dlatego po ponownym wyświetleniu strony należy ponownie uzyskać blokadę wybudzania.

Znajdź plik wake_lock.mjs i dodaj do niego poniższą treść. Aby sprawdzić, czy to działa, skonfiguruj wygaszacz ekranu tak, aby wyświetlał się po minucie.

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. 🐟 Dodanie obsługi interfejsu Periodic Background Sync API

Zaczynanie od pustego obszaru roboczego może być nudne. Możesz użyć interfejsu Periodic Background Sync API, aby codziennie inicjować obszar roboczy użytkowników nowym obrazem, np. zdjęciem fugu z Unsplash.

Wymaga to 2 plików: pliku periodic_background_sync.mjs, który rejestruje okresową synchronizację w tle, i pliku image_of_the_day.mjs, który odpowiada za pobieranie zdjęcia dnia.

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

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. 🐟 Dodawanie obsługi interfejsu Shape Detection API

Rysunki użytkowników lub użyte obrazy tła mogą czasami zawierać przydatne informacje, np. kody kreskowe. Shape Detection API, a w szczególności Barcode Detection API, umożliwia wyodrębnianie tych informacji. Dodaj funkcję, która próbuje wykrywać kody kreskowe na rysunkach użytkowników. Odszukaj plik barcode.mjs i dodaj do niego poniższą zawartość. Aby przetestować tę funkcję, wczytaj lub wklej na obszar roboczy obraz z kodem kreskowym. Możesz skopiować przykładowy kod kreskowy z wyszukiwania obrazów kodów 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. 🐡 Dodawanie obsługi interfejsu Idle Detection API

Jeśli wyobrazisz sobie, że aplikacja działa w konfiguracji podobnej do kiosku, przydatną funkcją będzie zresetowanie obszaru roboczego po pewnym czasie bezczynności. Interfejs Idle Detection API umożliwia wykrywanie, kiedy użytkownik przestaje korzystać z urządzenia.

Znajdź plik idle_detection.mjs i wklej do niego poniższą zawartość.

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. 🐡 Dodawanie obsługi interfejsu File Handling API

A gdyby użytkownicy mogli po prostu kliknąć dwukrotnie plik obrazu, a Twoja aplikacja by się pojawiła? Umożliwia to interfejs File Handling API.

Musisz zarejestrować PWA jako program obsługi plików obrazów. Dzieje się to w manifeście aplikacji internetowej. Pokazuje to poniższy fragment pliku manifest.webmanifest. (Jest to już część pliku manifestu, nie musisz go dodawać samodzielnie).

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

Aby obsługiwać otwarte pliki, dodaj do pliku file-handling.mjs ten kod:

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. Gratulacje

🎉 Gratulacje!

W ramach projektu Fugu 🐡 powstaje tak wiele ciekawych interfejsów API przeglądarki, że ten przewodnik może zaledwie zarysować temat.

Jeśli chcesz dowiedzieć się więcej, zapoznaj się z naszymi publikacjami w witrynie web.dev.

Strona docelowa sekcji „Możliwości” w witrynie web.dev.

Ale to nie koniec. W przypadku aktualizacji, które nie zostały jeszcze upublicznione, możesz skorzystać z naszego narzędzia do śledzenia interfejsu Fugu API, które zawiera linki do wszystkich propozycji, które zostały wdrożone, są w fazie testów origin lub testów deweloperskich, wszystkich propozycji, nad którymi rozpoczęto pracę, oraz wszystkich propozycji, które są rozważane, ale nie zostały jeszcze rozpoczęte.

Witryna śledząca interfejs Fugu API

Ten codelab został napisany przez Thomasa Steinera (@tomayac). Chętnie odpowiem na Twoje pytania i czekam na Twoją opinię. Specjalne podziękowania dla Hemantha H.M (@GNUmanth), Christiana Liebela (@christianliebel), Svena Maya (@Svenmay), Larsa Knudsena (@larsgk) i Jackie Han (@hanguokai), którzy pomogli w przygotowaniu tego laboratorium.