Stopniowo ulepszaj progresywną aplikację internetową

Tworzenie z myślą o nowoczesnych przeglądarkach i stopniowe ulepszanie, tak jak w 2003 r.

W marcu 2003 roku Nick Finck Steve Champeon oszołomił świat projektowania stron internetowych z pojęciem progresywne ulepszenie, strategię projektowania stron, która najpierw kładzie nacisk na wczytywanie głównych treści strony, i dodają kolejne niuanse, z technicznie rygorystycznymi warstwami prezentacji i funkcjami. W 2003 r. rozwój progresywny polegał na wykorzystaniu – funkcje CSS, dyskretny JavaScript, a nawet tylko skalowalną grafikę wektorową. Stopniowe udoskonalenia w 2020 roku i później będą polegać możliwości nowej wersji przeglądarki.

Projektowanie stron promujących integrację społeczną z myślą o przyszłości z progresywnymi ulepszeniami. Slajd tytułowy z oryginalnej prezentacji Fincka i Champeona.
Slide: inkluzywne projektowanie stron internetowych z myślą o przyszłości dzięki stopniowemu udoskonalaniu. (Źródło)

Nowoczesny JavaScript

A skoro już o tym mowa, to sytuacja, w której przeglądarki są obsługiwane w najnowszym podstawowym kodzie JavaScript w ES 2015. jest bardzo przydatna. Nowy standard obejmuje obietnice, moduły, klasy, literały szablonów, funkcje strzałek, let i const, parametry domyślne, generatory, przypisanie zniszczenia, odpoczynek i rozprzestrzenianie, Map/Set, WeakMap/WeakSet i wiele innych. Wszystkie są obsługiwane.

Tabela pomocy CanIUse dla funkcji ES6 pokazująca ich obsługę we wszystkich popularnych przeglądarkach.
Tabela obsługi przeglądarki ECMAScript 2015 (ES6). (Źródło)

Funkcje asynchroniczne, czyli funkcja w wersji ES 2017, jeden z moich ulubionych, można użyć we wszystkich popularnych przeglądarkach. Słowa kluczowe async i await umożliwiają działanie asynchroniczne, oparte na obietnicach być napisane w bardziej przejrzystym stylu, co pozwala uniknąć konieczności jawnego konfigurowania łańcuchów obietnic.

Tabela pomocy CanIUse dla funkcji asynchronicznych, pokazująca ich obsługę we wszystkich popularnych przeglądarkach.
Tabela funkcji asynchronicznych, w których są obsługiwane przeglądarki. (Źródło)

Oprócz tego wprowadziliśmy niedawno nowe języki do prezentacji w Hiszpanii w 2020 roku, opcjonalnego łańcucha oraz łączenie nullish szybko skontaktowali się z zespołem pomocy. Poniżej znajdziesz przykładowy kod. Jeśli chodzi o podstawowe funkcje JavaScriptu, trawa nie może być bardziej ekologiczna. jest dzisiaj.

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
. Kultowy obraz tła zielonej trawy systemu Windows XP.
Jeśli chodzi o podstawowe funkcje JavaScriptu, trawa jest zielona. (Zrzut ekranu produktu firmy Microsoft, używany z pozwolenie).

Przykładowa aplikacja: Fugu Greetings

W tym artykule wykorzystam prostą aplikację PWA o nazwie Fugu Powitanie (GitHub). Nazwa tej aplikacji to zdrada dla Projektu Fugu 🐡, który ma na celu dać internet wszystkim możliwości aplikacji na Androida, iOS i komputery. Więcej informacji o tym projekcie znajdziesz na stronie stronie docelowej.

Fugu Greetings to aplikacja do rysowania, która umożliwia tworzenie wirtualnych kartek z życzeniami u Twoich bliskich. Jest to przykład Podstawowe zagadnienia dotyczące PWA. Jest niezawodne i działające w trybie offline, więc nawet jeśli ma sieć, wciąż możesz z niej korzystać. Jest również możliwa do zainstalowania do ekranu głównego urządzenia i bezproblemowo integruje się z systemem operacyjnym jako samodzielnej aplikacji.

Aplikacja Fugu Greetings PWA z rysunkiem przypominającym logo społeczności PWA.
Przykładowa aplikacja Fugu Greetings.
.

Stopniowe ulepszanie

Teraz czas przejść do progresywnego ulepszania. Słowniczek dokumentów internetowych MDN definiuje w następujący sposób:

Stopniowe doskonalenie to filozofia projektowania, na której dostęp do najważniejszych treści i funkcji jak największej liczbie użytkowników, zapewnienie użytkownikom jak najlepszych wrażeń tylko użytkownikom najnowszych technologii które mogą uruchomić cały wymagany kod.

Wykrywanie cech służy zwykle do określania, czy przeglądarki obsługują bardziej nowoczesne funkcje, podczas gdy polyfill są często wykorzystywane do dodawania brakujących funkcji za pomocą JavaScriptu.

[…]

Stopniowe ulepszanie to przydatna technika, która pozwala programistom skupić się w tworzeniu jak najlepszych witryn i dbaniu o ich działanie na wielu nieznanych klientach użytkownika. Pogorszenie z wrogością jest powiązane, ale nie jest tym samym, i często postrzega się je jako zmierzenie w przeciwnym kierunku do stopniowego ulepszania. W rzeczywistości oba podejścia są prawidłowe i często się wzajemnie uzupełniają.

Współtwórcy MDN

Tworzenie każdej pocztówki od zera może być uciążliwe. Warto więc wprowadzić funkcję, która pozwala użytkownikom zaimportować obraz i od tej pory zacząć z niego korzystać. Przy tradycyjnym podejściu konieczne byłoby użycie <input type=file> . Najpierw musisz utworzyć element, ustawić jego type na 'file' i dodać typy MIME do właściwości accept. a następnie programowo „klikać” i monitorować zmiany. Gdy wybierzesz zdjęcie, zostanie ono zaimportowane bezpośrednio do przestrzeni roboczej.

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

Gdy dostępna jest funkcja importowania, prawdopodobnie powinna być też opcja eksportu. dzięki czemu użytkownicy mogą zapisać swoje pocztówki lokalnie. Tradycyjnym sposobem zapisywania plików jest utworzenie linku do kotwicy, z download z adresem URL obiektu blob jako wartością href. Możesz też automatycznie klikać w celu zainicjowania procesu pobierania, Aby zapobiec wyciekom pamięci, nie zapomnij unieważnić adresu URL obiektu blob.

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

Moment. Nic nie zostało pobrane pocztówkę, „zapisano” . Zamiast wyświetlać przycisk „zapisz” w oknie dialogowym, które pozwala wybrać, gdzie umieścić plik, przeglądarka bezpośrednio pobrała pocztówkę bez interakcji użytkownika. i przeniosła go prosto do folderu Pobrane. Nie jest to dobre.

A gdyby było lepsze rozwiązanie? A gdyby po prostu otworzyć plik lokalny, edytować go, a następnie zapisać zmiany, do nowego pliku lub z powrotem do pierwotnego pliku, który został przez Ciebie otwarty? Okazuje się, że istnieje. Interfejs File System Access API umożliwia otwieranie i tworzenie plików oraz ich modyfikowanie i zapisywanie .

Jak więc wykryć interfejs API? Interfejs File System Access API udostępnia nową metodę window.chooseFileSystemEntries(). W zależności od tego, czy ta metoda jest dostępna, muszę warunkowo ładować różne moduły importu i eksportu. Poniżej pokażę, jak to zrobić.

const loadImportAndExport = () => {
  if ('chooseFileSystemEntries' in window) {
    Promise.all([
      import('./import_image.mjs'),
      import('./export_image.mjs'),
    ]);
  } else {
    Promise.all([
      import('./import_image_legacy.mjs'),
      import('./export_image_legacy.mjs'),
    ]);
  }
};

Zanim jednak omówię szczegółowo interfejs File System Access API, Krótko omówię schemat postępującego ulepszenia. W przeglądarkach, które obecnie nie obsługują interfejsu File System Access API, ładuję starsze skrypty. Karty sieci w przeglądarkach Firefox i Safari znajdziesz poniżej.

Inspektor sieci w Safari pokazujący ładowane starsze pliki.
Karta sieci Inspektora sieci w Safari.
.
.
Narzędzia dla programistów w przeglądarce Firefox pokazujące wczytywane starsze pliki.
Karta sieciowa narzędzi dla programistów w przeglądarce Firefox.

Jednak w przeglądarce Chrome, która obsługuje ten interfejs API, ładowane są tylko nowe skrypty. To elegancko możliwe dzięki dynamiczna import(), która działa we wszystkich nowoczesnych przeglądarkach. pomocy. Jak powiedziałem wcześniej, trawa jest obecnie dość zielona.

Narzędzia deweloperskie w Chrome pokazujące wczytywanie nowoczesnych plików.
Karta sieciowa w Narzędziach deweloperskich w Chrome.

Interfejs File System Access API

Po omówieniu tego tematu czas przyjrzeć się faktycznej implementacji opartej na interfejsie File System Access API. Aby zaimportować obraz, nazywam window.chooseFileSystemEntries() i przekazać mu właściwość accepts, w której zaznaczę, że chcę pliki graficzne. Obsługiwane są zarówno rozszerzenia plików, jak i typy MIME. W efekcie powstanie uchwyt pliku, z którego mogę uzyskać prawidłowy plik, wywołując funkcję getFile().

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

Eksportowanie obrazu wygląda prawie tak samo, ale tym razem Muszę przekazać parametr typu 'save-file' do metody chooseFileSystemEntries(). Pojawi się okno zapisywania pliku. Po otwarciu pliku nie było to konieczne, ponieważ domyślnie jest to 'open-file'. Ustawiam parametr accepts podobnie jak poprzednio, ale tym razem ograniczam się do obrazów PNG. Znowu zwracam uchwyt pliku, ale zamiast pobierać plik, tym razem utworzę strumień możliwy do zapisu, wywołując funkcję createWritable(). Następnie piszę w pliku blob, czyli obraz mojej pocztówki. Na koniec zamykam strumień możliwy do zapisu.

Wszystko może zawsze zawieść: na dysku może brakować miejsca, mógł wystąpić błąd zapisu lub odczytu albo po prostu użytkownik zamknął okno dialogowe pliku. Dlatego zawsze dodaję wywołania do instrukcji try...catch.

const exportImage = async (blob) => {
  try {
    const handle = await window.chooseFileSystemEntries({
      type: 'save-file',
      accepts: [
        {
          description: 'Image file',
          extensions: ['png'],
          mimeTypes: ['image/png'],
        },
      ],
    });
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

Korzystając z progresywnego ulepszania za pomocą interfejsu File System Access API, Mogę otworzyć plik tak jak poprzednio. Zaimportowany plik jest narysowany bezpośrednio w obszarze roboczym. Mogę wprowadzać zmiany i zapisywać je w oknie dialogowym z prawdziwym zapisywaniem gdzie mogę wybrać nazwę i lokalizację pliku. Teraz plik jest gotowy do zachowania bez końca.

Aplikacja Fugu Greetings z otwartym oknem pliku.
Okno otwierania pliku.
.
.
Aplikacja Fugu Greetings z zaimportowanym obrazem.
Zaimportowany obraz.
.
.
Aplikacja Fugu Greetings ze zmodyfikowanym obrazem.
Zapisywanie zmodyfikowanego obrazu w nowym pliku.

Interfejsy API związane z udziałem w internecie i docelowym udziałem w internecie

Oprócz przechowywania na zawsze będę chcieć udostępnić swoją pocztówkę. Są one dostępne dla interfejsów Web Share API oraz Pozwala mi to w interfejsie Web Share Target API. W systemach operacyjnych na urządzenia mobilne, a od niedawna także na komputery, funkcja udostępniania jest dostępna mechanizmów ochrony danych. Poniżej znajduje się przykładowy arkusz udostępniania przeglądarki Safari na komputerze w systemie macOS uruchomiony z poziomu artykułu w mojego bloga. Gdy klikniesz przycisk Udostępnij artykuł, możesz udostępnić link do artykułu znajomemu. na przykład w aplikacji Wiadomości w systemie macOS.

Arkusz udostępniania Safari na komputerze w systemie macOS uruchamiany po kliknięciu przycisku Udostępnij artykuł
Interfejs Web Share API w Safari na komputerze w systemie macOS.

Kod, który to umożliwia, jest dość prosty. Dzwonię do: navigator.share() i przekazać jej opcjonalne wartości title, text i url w obiekcie. Co w sytuacji, gdy chcę załączyć obraz? Poziom 1 interfejsu Web Share API jeszcze nie obsługuje tej funkcji. Na szczęście na poziomie 2 udziału w udostępnianiu w internecie pojawiły się funkcje udostępniania plików.

try {
  await navigator.share({
    title: 'Check out this article:',
    text: `"${document.title}" by @tomayac:`,
    url: document.querySelector('link[rel=canonical]').href,
  });
} catch (err) {
  console.warn(err.name, err.message);
}

Pokażę Ci, jak to zrobić w aplikacji do karty z życzeniami Fugu. Najpierw muszę przygotować obiekt data z tablicy files składającej się z jednego obiektu blob, a potem title i text. Następnie używam nowej metody navigator.canShare(), która: co sugeruje jego nazwa: Wskazuje, czy obiekt data, który próbuję udostępnić, może być technicznie udostępniany przez przeglądarkę. Jeśli navigator.canShare() poinformuje mnie, że dane można udostępnić, tak jak wcześniej, wywołaj navigator.share(). Wszystko może zawieść, więc znów używam blokady try...catch.

const share = async (title, text, blob) => {
  const data = {
    files: [
      new File([blob], 'fugu-greeting.png', {
        type: blob.type,
      }),
    ],
    title: title,
    text: text,
  };
  try {
    if (!(navigator.canShare(data))) {
      throw new Error("Can't share data.", data);
    }
    await navigator.share(data);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

Tak jak wcześniej używam ulepszenia progresywnego. Jeśli w obiekcie navigator są zarówno 'share', jak i 'canShare', tylko wtedy przejdę dalej i Wczytywanie share.mjs za pomocą dynamicznego import(). W przeglądarkach, takich jak Safari na urządzeniach mobilnych, które spełniają tylko jeden z dwóch warunków, nie wczytuję strony przed uruchomieniem.

const loadShare = () => {
  if ('share' in navigator && 'canShare' in navigator) {
    import('./share.mjs');
  }
};

Jeśli w Fugu Greetings kliknę przycisk Udostępnij na obsługiwanej przeglądarce, takiej jak Chrome na Androida, otworzy się wbudowany arkusz udostępniania. Mogę na przykład wybrać Gmaila, a wyświetli się widżet narzędzia do tworzenia e-maili obraz załączony.

Arkusz udostępniania na poziomie systemu operacyjnego przedstawiający różne aplikacje, którym można udostępnić obraz.
wybierając aplikację, w której chcesz udostępnić plik;
.
.
Widżet tworzenia e-maili w Gmailu z załączonym obrazem.
Plik zostanie dołączony do nowej wiadomości w narzędziu Gmail.

Interfejs API selektora kontaktów

Teraz chcę porozmawiać o kontaktach, czyli o książce adresowej urządzenia lub w aplikacji do zarządzania kontaktami. Podczas pisania pocztówki poprawne pisanie może nie być łatwe czyjeś imię i nazwisko. Mam na przykład znajomego Sergeya, który chce, by jego imię pisze się cyrylicą. Jestem używa niemieckiej klawiatury QWERTZ i nie wiesz, jak wpisać imię. Możesz rozwiązać ten problem za pomocą interfejsu Contact Picker API. Mój znajomy jest zapisany w aplikacji Kontakty na telefonie, interfejsu API selektora kontaktów mogę otworzyć swoje kontakty w przeglądarce.

Najpierw muszę określić listę właściwości, do których chcę uzyskać dostęp. Tym razem chcę po prostu podać imiona i nazwiska, ale w innych przypadkach mogą mi pomóc numery telefonów, adresy e-mail, awatary. ikon i adresów pocztowych. Następnie konfiguruję obiekt options i ustawiam multiple na true, by móc zaznaczyć więcej niż jeden wpis. Mogę też wywołać funkcję navigator.contacts.select(), która zwraca odpowiednie właściwości. dla kontaktów wybranych przez użytkownika.

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

Wiesz już pewnie, jak wygląda ten proces: Wczytuję plik tylko wtedy, gdy interfejs API jest rzeczywiście obsługiwany.

if ('contacts' in navigator) {
  import('./contacts.mjs');
}

W Fugu Greeting klikam przycisk Contacts (Kontakty) i wybieram dwóch najlepszych przyjaciół, Бергей مийлович Брин i 劳伦斯·爱德用·"拉里"·佩奇, zobaczysz, jak jest ograniczone do wyświetlania tylko ich imion i nazwisk, ale nie ich adresów e-mail ani innych informacji, takich jak numery telefonów. Ich imiona są następnie rysowane na kartce z życzeniami.

Selektor kontaktów pokazujący nazwy 2 kontaktów w książce adresowej.
Wybranie dwóch nazw za pomocą selektora kontaktów z książki adresowej.
.
.
Nazwy 2 wcześniej wybranych kontaktów narysowane na pocztówce.
Obie nazwy zostaną narysowane na kartce z życzeniami.

Interfejs Asynchronous Clipboard API

Kolejny krok to kopiowanie i wklejanie. Jedną z naszych ulubionych działań dla twórców oprogramowania jest kopiowanie i wklejanie. Jako autor pocztówki z życzeniami mogę chcieć zrobić to samo. Mogę wkleić obraz do pocztówki, nad którą pracuję, lub skopiuj moją pocztówkę, aby móc ją edytować w innym miejscu. interfejs Async Clipboard API, obsługuje zarówno tekst, jak i obrazy. Pozwól, że omówię, jak dodano obsługę kopiowania i wklejania do Fugu. Aplikacja Pozdrowienia.

Aby skopiować coś do schowka systemowego, muszę w nim coś napisać. Metoda navigator.clipboard.write() przyjmuje tablicę elementów schowka jako . Każdy element schowka jest zasadniczo obiektem, którego wartością jest obiekt blob i jego typ jako klucz.

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

Aby wkleić elementy, muszę zapętlić elementy schowka uzyskane za pomocą wywołania navigator.clipboard.read() Dzieje się tak, ponieważ w schowku może znajdować się wiele elementów przedstawiania informacji w różny sposób. Każdy element schowka ma pole types z informacjami o typach MIME dostępnych danych i zasobami Google Cloud. Wywołuję metodę getType() elementu schowka, przekazując parametr Typ MIME uzyskany wcześniej.

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

Na razie nie trzeba tego robić. Robię to tylko w obsługiwanych przeglądarkach.

if ('clipboard' in navigator && 'write' in navigator.clipboard) {
  import('./clipboard.mjs');
}

Jak to działa w praktyce? Mam otwarty obraz w aplikacji macOS Preview i skopiuj go do schowka. Gdy kliknę Wklej, aplikacja Fugu Greetings pyta mnie czy chcę zezwolić aplikacji na dostęp do tekstu i obrazów w schowku.

Aplikacja Fugu Greetings wyświetla prośbę o przyznanie uprawnień do korzystania ze schowka.
Prośba o przyznanie uprawnień do korzystania ze schowka.

Na koniec, po zaakceptowaniu uprawnień, obraz jest wklejony do aplikacji. Działa też odwrotna strona. Pozwól mi skopiować pocztówkę do schowka. Gdy otwieram Podgląd i klikam Plik, a następnie Nowy ze schowka. karta z życzeniami zostanie wklejona do nowego obrazu bez tytułu.

Aplikacja testowa macOS z właśnie wklejonym obrazem bez tytułu.
Obraz wklejony do aplikacji testowej macOS.

Interfejs Badging API

Innym przydatnym interfejsem API jest Badging API. Fugu Greetings to oczywiście instalowana aplikacja PWA, które użytkownicy mogą umieścić w docku z aplikacjami lub na ekranie głównym. Ciekawym i łatwym sposobem zaprezentowania interfejsu API jest (nadużywanie) go w Fugu Greetings. jak licznik pociągnięć piórem. Dodałem detektor zdarzeń, który zwiększa licznik pociągnięć piórem za każdym razem, gdy wystąpi zdarzenie pointerdown. i ustawia nową plakietkę. Po każdym wyczyszczeniu obszaru roboczego licznik resetuje się, a plakietka zostaje usunięta.

let strokes = 0;

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

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

Jest to ulepszenie progresywne, więc proces ładowania przebiega w normalny sposób.

if ('setAppBadge' in navigator) {
  import('./badge.mjs');
}

W tym przykładzie wpisałem liczby od jednego do siedmiu, używając jednego przekreślenia pióra na numer. Licznik plakietek na ikonie wynosi teraz siedem.

Na pocztówce rysowane są liczby od 1 do 7. Każda z nich ma po jednym przekreśleniu pióra.
Rysujemy liczby od 1 do 7, używając 7 przekreśleń pióra.
.
.
Ikona plakietki w aplikacji Fugu Greetings z cyfrą 7.
Licznik pociągnięć piórem w postaci plakietki ikony aplikacji.

Interfejs Periodic Background Sync API

Chcesz zacząć każdy dzień od czegoś nowego? Zaletą aplikacji Fugu Greetings jest to, że każdego ranka może ona być inspiracją wybierz nowy obraz tła, aby rozpocząć pocztówkę. Aplikacja używa interfejsu Periodic Background Sync API. w osiągnięciu tego celu.

Pierwszym krokiem jest zarejestrowanie zdarzenia synchronizacji okresowej w rejestracji mechanizmów Service Worker. Nasłuchuje tagu synchronizacji 'image-of-the-day' a minimalny odstęp czasu to jeden dzień, dzięki czemu użytkownik może co 24 godziny pobierać nowy obraz tła.

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

Drugim krokiem jest nasłuchiwanie zdarzenia periodicsync w skrypcie service worker. Jeśli tag zdarzenia to 'image-of-the-day', czyli ten, który został wcześniej zarejestrowany, obraz dnia jest pobierany przez funkcję getImageOfTheDay(), a wyniki są rozpowszechnione wśród wszystkich klientów, dzięki czemu mogą zaktualizować pamięci podręcznej.

self.addEventListener('periodicsync', (syncEvent) => {
  if (syncEvent.tag === 'image-of-the-day-sync') {
    syncEvent.waitUntil(
      (async () => {
        const blob = await getImageOfTheDay();
        const clients = await self.clients.matchAll();
        clients.forEach((client) => {
          client.postMessage({
            image: blob,
          });
        });
      })()
    );
  }
});

Jest to w rzeczywistości progresywne ulepszenie, więc kod jest ładowany tylko wtedy, gdy Interfejs API jest obsługiwany przez przeglądarkę. Dotyczy to zarówno kodu klienta, jak i kodu skryptu service worker. W nieobsługiwanych przeglądarkach żadna z nich nie zostanie wczytana. Zwróć uwagę, jak to działa w skrypcie service worker, a nie w dynamicznym import() (nie jest obsługiwane w kontekście skryptu service worker jeszcze), Używam klasycznej wersji importScripts()

// In the client:
const registration = await navigator.serviceWorker.ready;
if (registration && 'periodicSync' in registration) {
  import('./periodic_background_sync.mjs');
}
// In the service worker:
if ('periodicSync' in self.registration) {
  importScripts('./image_of_the_day.mjs');
}

Naciśnięcie przycisku Tapeta w Fugu Greetings powoduje wyświetlenie obrazu kartki z życzeniami dnia. aktualizowany codziennie za pomocą interfejsu Periodic Background Sync API.

Aplikacja z życzeniami Fugu z nową grafiką dnia.
Naciśnięcie przycisku Tapeta powoduje wyświetlenie obrazu dnia.

Interfejs Notification Triggers API

Czasem nawet przy dużej motywacji wystarczy motywacja, by dokończyć powitanie Tę funkcję włącza interfejs Notification Triggers API. Jako użytkownik mogę wpisać godzinę, o której trzeba będzie przeczytać pocztówkę. Gdy nadejdzie ta chwila, otrzymam powiadomienie, że czeka na mnie kartka z życzeniami.

Gdy pojawi się prośba o podanie czasu docelowego, aplikacja planuje powiadomienie za pomocą konstrukcji showTrigger. Może to być TimestampTrigger z wybraną wcześniej datą docelową. Przypomnienie zostanie wyświetlone lokalnie, bez konieczności użycia sieci czy serwera.

const targetDate = promptTargetDate();
if (targetDate) {
  const registration = await navigator.serviceWorker.ready;
  registration.showNotification('Reminder', {
    tag: 'reminder',
    body: "It's time to finish your greeting card!",
    showTrigger: new TimestampTrigger(targetDate),
  });
}

To jest stopniowe ulepszenie, więc kod jest ładowany tylko warunkowo.

if ('Notification' in window && 'showTrigger' in Notification.prototype) {
  import('./notification_triggers.mjs');
}

Gdy zaznaczę pole Reminder (Przypomnienie) w Fugu Greetings, pojawia się pytanie kiedy chcę otrzymać przypomnienie o dokończeniu pocztówki.

Aplikacja Fugu Greetings z pytaniem, kiedy użytkownik chce otrzymać przypomnienie o konieczności dokończenia kartki okolicznościowej.
Planowanie wyświetlania powiadomienia lokalnego o konieczności ukończenia kartki z życzeniami.

Gdy w Fugu Greetings zostanie aktywowane zaplanowane powiadomienie, wyświetla się tak jak każde inne powiadomienie, ale tak jak pisałem wcześniej, połączenie z internetem nie wymagało połączenia sieciowego.

Centrum powiadomień macOS z wyświetlonym powiadomieniem z Fugu Greetings.
Uruchomione powiadomienie pojawi się w Centrum powiadomień macOS.

Interfejs API Wake Lock

Chcę też uwzględnić interfejs Wake Lock API. Czasami wystarczy patrzeć w ekran wystarczająco długo, aby znaleźć inspirację całuje Cię. Najgorsze, co może się wtedy stać, to wyłączenie ekranu. Interfejs API Wake Lock może temu zapobiec.

Pierwszym krokiem jest uzyskanie blokady uśpienia za pomocą urządzenia navigator.wakelock.request method(). Przekazuję do niego ciąg 'screen', aby uzyskać blokadę uśpienia ekranu. Dodaję teraz detektor zdarzeń, który będzie informował o zwoleniu blokady uśpienia. Może się tak na przykład zdarzyć, gdy zmieni się widoczność karty. Jeśli tak się stanie, mogę ponownie włączyć blokadę uśpienia, gdy karta stanie się ponownie widoczna.

let wakeLock = null;
const requestWakeLock = async () => {
  wakeLock = await navigator.wakeLock.request('screen');
  wakeLock.addEventListener('release', () => {
    console.log('Wake Lock was released');
  });
  console.log('Wake Lock is active');
};

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

document.addEventListener('visibilitychange', handleVisibilityChange);
document.addEventListener('fullscreenchange', handleVisibilityChange);

Tak, jest to ulepszenie progresywne, dlatego muszę go ładować tylko wtedy, gdy przeglądarka obsługuje interfejs API.

if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
  import('./wake_lock.mjs');
}

W Fugu Greetings znajduje się pole wyboru Bezsenność, które po zaznaczeniu powoduje wybudzenie ekranu.

Jeśli to pole wyboru jest zaznaczone, ekran jest aktywny.
Pole wyboru Bezsenność sprawia, że aplikacja pozostaje aktywna.

Interfejs Idle Detection API

Czasami, nawet jeśli patrzysz w ekran przez wiele godzin, jest po prostu bezużyteczna i nie można wymyślić ani jednego pomysłu na pocztówkę. Interfejs Idle Detection API umożliwia aplikacji wykrywanie czasu bezczynności użytkownika. Jeśli użytkownik zbyt długo nie będzie nieaktywny, aplikacja zostanie przywrócona do stanu początkowego i wyczyści obszar roboczy. Ten interfejs API jest obecnie objęty ograniczeniami uprawnienia do wyświetlania powiadomień, bo w wielu produkcyjnych zastosowaniach wykrywania bezczynności są związane z powiadomieniami. np. aby wysłać powiadomienie tylko na urządzenie, z którego użytkownik obecnie korzysta.

Po upewnieniu się, że została przyznana zgoda na wyświetlanie powiadomień, detektor bezczynności. Rejestruję detektor zdarzeń, który nasłuchuje zmian w przypadku braku aktywności. Zawiera on użytkownika stanu ekranu. użytkownik może być aktywny lub nieaktywny, a ekran można odblokować lub odblokować. Jeśli użytkownik będzie nieaktywny, obszar roboczy zostanie wyczyszczony. Określam wartość progową wykrywania bezczynności na 60 sekund.

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

await idleDetector.start({
  threshold: 60000,
  signal,
});

Jak zawsze wczytuję ten kod tylko wtedy, gdy przeglądarka go obsługuje.

if ('IdleDetector' in window) {
  import('./idle_detection.mjs');
}

W aplikacji Fugu Greetings obszar roboczy jest usuwany, gdy pole wyboru Efemeryczny jest zaznaczone. jest zaznaczona, a użytkownik zbyt długo jest nieaktywny.

Aplikacja Fugu Greetings z wyczyszczonym obszarem roboczym po zbyt długim okresie bezczynności.
Gdy pole wyboru Efemeryczny jest zaznaczone, a użytkownik był zbyt długo bezczynny, obszar roboczy zostanie wyczyszczony.

Zakończenie

Cóż, co za podróż. Tyle interfejsów API w jednej przykładowej aplikacji. Pamiętaj, że nigdy nie zmuszam użytkowników do ponoszenia kosztów pobierania w przypadku funkcji, której ich przeglądarka nie obsługuje. Dzięki ulepszeniu progresywnemu masz pewność, że wczytuje się tylko odpowiedni kod. A ponieważ w przypadku HTTP/2 żądania są tanie, ten wzorzec powinien działać aplikacji, chociaż w przypadku bardzo dużych aplikacji możesz rozważyć tworzenie pakietu.

Panel sieci w Narzędziach deweloperskich w Chrome, który pokazuje tylko żądania plików z kodem obsługiwanym przez bieżącą przeglądarkę.
Karta Network (Sieć) w Narzędziach deweloperskich w Chrome zawierająca tylko żądania plików z kodem obsługiwanym przez bieżącą przeglądarkę.

Aplikacja może wyglądać trochę inaczej w każdej przeglądarce, ponieważ nie wszystkie platformy obsługują wszystkie funkcje, ale główna funkcjonalność jest zawsze dostępna – stopniowo ulepszana zgodnie z możliwościami konkretnej przeglądarki. Pamiętaj, że te możliwości mogą się zmienić nawet w jednej i tej samej przeglądarce, w zależności od tego, czy aplikacja działa jako zainstalowana czy też na karcie przeglądarki.

Fugu Greetings działająca w Chrome na Androida, pokazująca wiele dostępnych funkcji.
Aplikacja Fugu Greetings uruchomiona w Chrome na Androida.
.
.
Powitanie Fugu działa w przeglądarce Safari na komputerze i wyświetla mniej dostępnych funkcji.
Aplikacja Fugu Greetings uruchamiana w przeglądarce Safari na komputerze.
.
.
Fugu Greetings działająca w Chrome na komputerze, pokazująca wiele dostępnych funkcji.
Aplikacja Fugu Greetings działa w Chrome na komputerze.

Jeśli interesuje Cię aplikacja Fugu Greetings, znajdź go i rozwidlej na GitHubie.

Repozytorium Fugu Greetings na GitHubie.
aplikację Fugu Greetings w GitHubie.

Zespół Chromium pracuje nad tym, aby zaawansowane interfejsy API Fugu były bardziej ekologiczne. Przez stopniowe ulepszanie aplikacji Dbam o to, aby wszyscy mieli dobre, solidne podstawy. ale użytkownicy korzystający z przeglądarek obsługujących więcej interfejsów API platformy internetowej mają jeszcze lepsze wrażenia. Jesteśmy ciekawi, co uda Ci się osiągnąć dzięki progresywnym ulepszeniom w swoich aplikacjach.

Podziękowania

Dziękuję Christian Liebel i Hemanth HM, który współuczestniczy w wydarzeniu Fugu Greetings. Ten artykuł został zrecenzowany przez Joe Medley i Kayce Basques Jake Archibald pomógł mi poznać sytuację. z dynamicznym import() w kontekście mechanizmu Service Worker.