Tworzenie z myślą o nowoczesnych przeglądarkach i stopniowe ulepszanie, tak jak w 2003 r.
W marcu 2003 r. Nick Finck i Steve Champeon zaskoczyli świat projektowania stron internetowych koncepcją ulepszania progresywnego, czyli strategii projektowania stron internetowych, która kładzie nacisk na wczytywanie najpierw głównej zawartości strony, a potem stopniowo dodaje do niej coraz bardziej zaawansowane technicznie warstwy prezentacji i funkcji. W 2003 r. ulepszanie stron stopniowo polegało na korzystaniu z obecnie nowoczesnych funkcji CSS, nieinwazyjnego JavaScriptu, a nawet tylko skalowanych grafik wektorowych. Progresywne ulepszanie w 2020 roku i później polega na korzystaniu z funkcji nowoczesnych przeglądarek.
Nowoczesny JavaScript
A gdy mowa o JavaScript, przeglądarki świetnie obsługują najnowsze funkcje JavaScriptu z rdzenia ES 2015.
Nowy standard obejmuje obietnice, moduły, klasy, literały szablonów, funkcje strzałki, let
i const
, parametry domyślne, generatory, przypisanie destrukturyzowane, rest i spread, Map
/Set
, WeakMap
/WeakSet
i wiele innych.
Wszystkie są obsługiwane.
Funkcje asynchroniczne, które są funkcją ES 2017 i jedną z moim ulubionych, można używać we wszystkich popularnych przeglądarkach.
Słowa kluczowe async
i await
umożliwiają asynchroniczne, oparte na obietnicach zachowanie, które można zapisać w czytelniejszym stylu, co eliminuje potrzebę jawnego konfigurowania łańcuchów obietnic.
Nawet bardzo niedawne dodatki do języka ES 2020, takie jak opcjonalne łańcuchowanie i nullish coalescing, zostały szybko wdrożone. Poniżej znajdziesz przykładowy kod. Jeśli chodzi o podstawowe funkcje JavaScriptu, nie ma już lepszego miejsca niż to, w którym jesteś.
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah',
},
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
Przykładowa aplikacja: Fugu Greetings
W tym artykule korzystam z prostej aplikacji internetowej o nazwie Fugu Greetings (GitHub). Nazwa tej aplikacji nawiązuje do projektu Fugu 🐡, który miał na celu zapewnienie przeglądarce wszystkich możliwości aplikacji na Androida, iOS i komputery. Więcej informacji o tym projekcie znajdziesz na stronie docelowej.
Fugu Greetings to aplikacja do rysowania, która umożliwia tworzenie wirtualnych kartek okolicznościowych i wysyłanie ich do bliskich. Przykłady podstawowych pojęć PWA. Jest niezawodny i w pełni umożliwia pracę offline, więc możesz z niego korzystać nawet wtedy, gdy nie masz dostępu do sieci. Można go też zainstalować na ekranie głównym urządzenia i płynnie zintegrować z systemem operacyjnym jako samodzielną aplikację.
Stopniowe ulepszanie
Teraz czas na omówienie progresywnego ulepszania. W glosariuszu dokumentacji MDN dotyczącej stron internetowych określa się ten termin w ten sposób:
ulepszanie stopniowe to filozofia projektowania, która zapewnia podstawową zawartość i funkcjonalność dla jak największej liczby użytkowników, a zarazem zapewnia jak najlepszą obsługę tylko użytkownikom najnowocześniejszych przeglądarek, które mogą uruchamiać cały wymagany kod.
Wykrywanie funkcji jest zwykle używane do określania, czy przeglądarki obsługują bardziej nowoczesne funkcje. Z kolei do dodawania brakujących funkcji za pomocą JavaScriptu często używa się kodu polyfill.
[…]
Progresywne ulepszanie to przydatna technika, która pozwala deweloperom stron internetowych skupić się na tworzeniu jak najlepszych witryn, jednocześnie dbając o to, aby działały one na wielu nieznanych użytkownikach. Uprzejma degradacja jest powiązana z postępnym ulepszaniem, ale nie jest tym samym i często jest postrzegana jako działanie w przeciwnym kierunku. W rzeczywistości obie metody są prawidłowe i często mogą się wzajemnie uzupełniać.
Autorzy MND
Tworzenie każdej kartki z początku może być bardzo uciążliwe.
Dlaczego więc nie wprowadzić funkcji, która pozwoli użytkownikom importować obrazy i od razu z nich korzystać?
W przypadku tradycyjnego podejścia do tego problemu należałoby użyć elementu <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 „kliknąć” element i wsłuchać się w 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();
});
};
Jeśli istnieje funkcja importowania, powinna też być dostępna funkcja eksportowania, aby użytkownicy mogli zapisywać kartki okolicznościowe lokalnie.
Tradycyjny sposób zapisywania plików to utworzenie linku kotwicy z atrybutem download
i adresem URL obiektu blob jako href
.
Musisz też programowo „kliknąć” ten adres URL, aby wywołać pobieranie. Aby zapobiec wyciekom pamięci, nie zapomnij cofnąć 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. W sensie mentalnym nie „pobierasz” kartki z życzeniami, tylko ją „zapisujesz”. Zamiast wyświetlić okno „Zapisz”, w którym można wybrać miejsce docelowe pliku, przeglądarka pobrała kartkę okolicznościową bezpośrednio bez interakcji użytkownika i przeniosła ją bezpośrednio do folderu Pobrane. To nie jest dobre.
A co jeśli istnieje lepszy sposób? Co, jeśli można by otworzyć plik lokalny, edytować go, a następnie zapisać zmiany albo w nowym pliku, albo w pierwotnym pliku, który został otwarty na początku? Okazuje się, że jest. Interfejs File System Access API umożliwia otwieranie i tworzenie plików oraz katalogów, a także ich modyfikowanie i zapisywanie.
Jak wykryć interfejs API na podstawie funkcji?
Interfejs File System Access API udostępnia nową metodę window.chooseFileSystemEntries()
.
W związku z tym muszę warunkowo wczytywać różne moduły importu i eksportu w zależności od tego, czy dana metoda jest dostępna. 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ę interfejs API dostępu do systemu plików, chciałbym krótko omówić wzorce stopniowego ulepszania. 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.
W Chrome, czyli przeglądarce obsługującej interfejs API, wczytywane są tylko nowe skrypty.
Jest to możliwe dzięki dynamicznym import()
, które są obsługiwane przez wszystkie nowoczesne przeglądarki.
Jak już wspomniałem, obecnie jest bardzo zielono.
Interfejs File System Access API
Teraz, gdy już to wyjaśniliśmy, przyjrzyjmy się rzeczywistej implementacji na podstawie interfejsu File System Access API.
Aby zaimportować obraz, wywołuję funkcję window.chooseFileSystemEntries()
i przekazuję jej właściwość accepts
, w której podaję, z jakiego pliku obrazowego mają być pobrane dane.
Obsługiwane są zarówno rozszerzenia plików, jak i typy MIME.
W efekcie otrzymuję uchwyt pliku, z którego mogę pobrać rzeczywisty plik, wywołując 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 przebiega prawie tak samo, ale tym razem muszę przekazać parametr typu 'save-file'
metodzie chooseFileSystemEntries()
.
Otworzyło się okno zapisywania pliku.
W przypadku otwartego pliku nie było to konieczne, ponieważ domyślnie jest to 'open-file'
.
Parametr accepts
ustawiam podobnie jak wcześniej, ale tym razem ograniczam go tylko do obrazów PNG.
Ponownie otrzymuję uchwyt pliku, ale zamiast pobierania pliku tym razem tworzymy strumień do zapisu, wywołując funkcję createWritable()
.
Następnie zapisuję bloba, czyli obraz kartki z życzeniami, do pliku.
Na koniec zamykam strumień do zapisu.
Wszystko może się nie udać: może zabraknąć miejsca na dysku, wystąpić błąd zapisu lub odczytu albo użytkownik może po prostu zamknąć okno.
Dlatego zawsze otaczam wywołania instrukcją 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);
}
};
Dzięki stopniowemu ulepszaniu za pomocą interfejsu File System Access API mogę otworzyć plik tak jak wcześniej. Zaimportowany plik jest nanoszony bezpośrednio na obszar roboczy. Mogę wprowadzić zmiany i w końcu zapisać je w oknie dialogowym, w którym mogę wybrać nazwę i lokalizację pliku. Plik jest teraz gotowy do przechowywania na zawsze.
Interfejsy API związane z udziałem w internecie i docelowym udziałem w internecie
Poza przechowywaniem na wieczność może chcieć udostępnić swoją kartkę z życzeniami. Umożliwia to interfejs Web Share API i Web Share Target API. W systemach operacyjnych na urządzenia mobilne i komputery pojawiły się wbudowane mechanizmy udostępniania. Poniżej widać na przykład okno udostępniania w Safari na komputerze z macOS, które zostało wywołane z artykułu na naszym blogu. Gdy klikniesz przycisk Udostępnij artykuł, możesz udostępnić link do artykułu znajomemu, na przykład za pomocą aplikacji Wiadomości w systemie macOS.
Kod, który to umożliwia, jest dość prosty. Wywołuję funkcję navigator.share()
i przekazuję jej opcjonalne parametry title
, text
i url
w obiekcie.
Co jednak, jeśli chcę załączyć obraz? Poziom 1 interfejsu Web Share API nie obsługuje jeszcze tej funkcji.
Dobra wiadomość jest taka, że w ramach poziomu 2 udostępniania plików w przeglądarce dodano 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 przypadku aplikacji Fugu Greeting Card.
Najpierw muszę przygotować obiekt data
z tablicą files
zawierającą jeden blob, a następnie title
i text
. Zalecaną przez mnie metodą jest używanie nowej metody navigator.canShare()
, która działa zgodnie z jej nazwą:
Informuje mnie, 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ć, mogę zadzwonić pod numer navigator.share()
tak jak wcześniej.
Ponieważ wszystko może się nie udać, ponownie używam bloku 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);
}
};
Podobnie jak wcześniej, używam stopniowych ulepszeń.
Jeśli w obiekcie navigator
występują zarówno atrybuty 'share'
, jak i 'canShare'
, dopiero wtedy wczytuję share.mjs
za pomocą dynamicznego atrybutu import()
.
W przypadku przeglądarek takich jak Safari na urządzeniach mobilnych, które spełniają tylko 1 z 2 warunków, funkcja nie wczytuje się.
const loadShare = () => {
if ('share' in navigator && 'canShare' in navigator) {
import('./share.mjs');
}
};
Jeśli w Fugu Greetings kliknę przycisk Udostępnij w obsługiwanej przeglądarce, takiej jak Chrome na Androida, otworzy się wbudowany arkusz udostępniania. Mogę na przykład wybrać Gmaila, a widżet e-maila otworzy się z dołączonym obrazem.
Interfejs Contact Picker API
Teraz chcę porozmawiać o kontaktach, czyli książce adresowej na urządzeniu lub aplikacji do zarządzania kontaktami. Podczas pisania kartki z życzeniami nie zawsze łatwo jest poprawnie zapisać czyjeś imię i nazwisko. Mam na przykład znajomego Sergeya, który chce, by jego imię pisze się cyrylicą. Używam niemieckiej klawiatury QWERTZ i nie wiem, jak wpisać jej nazwę. Ten problem można rozwiązać za pomocą interfejsu Contact Picker API. Mam kontakt do znajomego zapisany w aplikacji Kontakty na telefonie, więc mogę go wybrać w interfejsie Contacts Picker API w przeglądarce.
Najpierw muszę określić listę usług, do których chcę uzyskać dostęp.
W tym przypadku interesują mnie tylko nazwy, ale w innych przypadkach mogę potrzebować numerów telefonów, adresów e-mail, awatarów, ikon lub adresów pocztowych.
Następnie konfiguruję obiekt options
i ustawiam wartość multiple
na true
, aby można było wybrać więcej niż 1 element.
Mogę też wywołać funkcję navigator.contacts.select()
, która zwraca odpowiednie właściwości 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);
}
};
Zapewne już znasz ten schemat: Wczytuję plik tylko wtedy, gdy interfejs API jest obsługiwany.
if ('contacts' in navigator) {
import('./contacts.mjs');
}
W Fugu Greeting, gdy klikam przycisk Kontakty i wybieram moich dwóch najlepszych kumpli, Сергей Михайлович Брин i 劳伦斯·爱德华·"拉里"·佩奇, widać, że selektor kontaktów wyświetla tylko ich imiona i nazwiska, ale nie adresy e-mail ani inne informacje, takie jak numery telefonów. Ich imiona są później narysowane na mojej karcie okolicznościowej.
Interfejs Asynchronous Clipboard API
Kolejna czynność to kopiowanie i wklejanie. Jako deweloperzy oprogramowania uwielbiamy kopiować i wklejać. Jako autor kartek okolicznościowych czasami chcę zrobić to samo. Możesz wkleić obraz na kartce okolicznościowej, nad którą pracujesz, lub skopiować kartkę, aby kontynuować jej edytowanie w innym miejscu. Interfejs Async Clipboard API obsługuje zarówno tekst, jak i obrazy. Pokażę Ci, jak dodać obsługę kopiowania i wklejania w aplikacji Fugu Greetings.
Aby skopiować coś do schowka systemowego, muszę coś w nim zapisać.
Metoda navigator.clipboard.write()
przyjmuje tablicę elementów schowka jako parametr.
Każdy element schowka to obiekt, którego wartość to blob, a klucz to typ bloba.
const copy = async (blob) => {
try {
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
}),
]);
} catch (err) {
console.error(err.name, err.message);
}
};
Aby wkleić, muszę przejść przez elementy schowka, które uzyskuję przez wywołanie navigator.clipboard.read()
.
Dzieje się tak, ponieważ na schowku może znajdować się wiele elementów w różnych reprezentacjach.
Każdy element skopiowany do schowka ma pole types
, które zawiera typy MIME dostępnych zasobów.
Wywołuję metodę getType()
elementu skopiowanego do schowka, przekazując wcześniej uzyskany typ MIME.
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);
}
};
Nie trzeba chyba tego tłumaczyć. 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 Podgląd na komputerze Mac i chcę go skopiować do schowka. Gdy klikam Wklej, aplikacja Fugu Greetings pyta, czy chcę zezwolić jej na dostęp do tekstu i obrazów ze schowka.
Gdy użytkownik zaakceptuje uprawnienia, obraz zostanie wklejony do aplikacji. Działa też odwrotna strona. Poczekaj, skopiuję kartkę z życzeniami na schowek. Gdy otworzysz Podgląd, klikniesz Plik, a potem Nowy z Schowka, karta z życzeniami zostanie wklejona do nowego obrazu bez tytułu.
Badging API
Innym przydatnym interfejsem API jest Badging API.
Jako instalowalna PWA Fugu Greetings ma oczywiście ikonę aplikacji, którą użytkownicy mogą umieścić w docku z aplikacjami lub na ekranie głównym.
Ciekawym i łatwym sposobem na zademonstrowanie interfejsu API jest jego wykorzystanie w programie Fugu Greetings jako licznika pociągnięć pióra.
Dodałem detektor zdarzeń, który zwiększa licznik pociągnięć pióra za każdym razem, gdy wystąpi zdarzenie pointerdown
, a następnie ustawia zaktualizowany znacznik ikony.
Gdy wyczyścisz płótno, licznik zostanie zresetowany, a plakietka zostanie usunięta.
let strokes = 0;
canvas.addEventListener('pointerdown', () => {
navigator.setAppBadge(++strokes);
});
clearButton.addEventListener('click', () => {
strokes = 0;
navigator.setAppBadge(strokes);
});
Ta funkcja jest ulepszaniem stopniowym, więc logika wczytywania jest taka sama jak zwykle.
if ('setAppBadge' in navigator) {
import('./badge.mjs');
}
W tym przykładzie rysuję cyfry od 1 do 7, używając pojedynczego pociągnięcia pióra na każdą cyfrę. Licznik na ikonie ma teraz wartość 7.
Interfejs Periodic Background Sync API
Chcesz zacząć każdy dzień od nowa? Ciekawą funkcją aplikacji Fugu Greetings jest to, że każdego ranka może ona inspirować Cię nowym obrazem tła, aby utworzyć pocztówkę. W tym celu aplikacja używa interfejsu Periodic Background Sync API.
Pierwszym krokiem jest register zdarzenia synchronizacji okresowej w rejestracji mechanizmów Service Worker.
Wysłuchuje tag synchronizacji o nazwie 'image-of-the-day'
. Ma minimalny interwał wynoszący 1 dzień, dzięki czemu użytkownik może co 24 godziny otrzymywać 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 usługach działających w tle.
Jeśli tag zdarzenia to 'image-of-the-day'
, czyli ten, który został zarejestrowany wcześniej, obraz dnia jest pobierany za pomocą funkcji getImageOfTheDay()
, a wynik jest propagowany do wszystkich klientów, aby mogli zaktualizować swoje kanwy i pamięć podręczną.
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 wprawdzie udoskonalenie stopniowe, więc kod wczytuje się tylko wtedy, gdy interfejs API jest obsługiwany przez przeglądarkę.
Dotyczy to zarówno kodu klienta, jak i kodu usługi.
W nieobsługiwanych przeglądarkach żaden z nich nie jest wczytywany.
Zwróć uwagę, że w skrypcie service worker zamiast dynamicznego obiektu import()
(którego nie jest jeszcze obsługiwane w kontekście skryptu service worker) używam klasycznego interfejsu 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');
}
W aplikacji Fugu Greetings naciśnięcie przycisku Tapeta powoduje wyświetlenie obrazka z kartki okolicznościowej na dany dzień, który jest aktualizowany codziennie za pomocą interfejsu API do okresowej synchronizacji w tle.
Notification Triggers API
Czasami nawet przy dużej inspiracji potrzebna jest pomoc, aby dokończyć rozpoczętą kartkę z życzeniami. Ta funkcja jest włączana przez interfejs Notification Triggers API. Jako użytkownik mogę określić czas, w którym chcę otrzymać przypomnienie o dokończeniu kartki z życzeniami. W odpowiednim momencie otrzymam powiadomienie, że moja kartka z życzeniami jest gotowa.
Gdy pojawi się prośba o podanie czasu docelowego, aplikacja zaplanuje powiadomienie za pomocą funkcji showTrigger
.
Może to być TimestampTrigger
z wybraną wcześniej datą docelową.
Powiadomienie o przypomnieniu zostanie wywołane lokalnie, bez udziału sieci ani 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),
});
}
Podobnie jak w przypadku wszystkich innych funkcji, które pokazałem do tej pory, jest to udoskonalenie stopniowe, więc kod jest wczytywany tylko warunkowo.
if ('Notification' in window && 'showTrigger' in Notification.prototype) {
import('./notification_triggers.mjs');
}
Gdy zaznaczę pole wyboru Przypomnienie w Fugu Greetings, pojawia się komunikat z pytaniem, kiedy mam otrzymać przypomnienie o dokończeniu kartki okolicznościowej.
Gdy zaplanowane powiadomienie zostanie uruchomione w Fugu Greetings, wyświetli się ono tak samo jak każde inne powiadomienie, ale jak już wspomniałem, nie wymaga połączenia z internetem.
Wake Lock API
Chcę też uwzględnić interfejs Wake Lock API. Czasami wystarczy tylko wpatrywać się w ekran, aż pojawi się inspiracja. Najgorsze, co może się stać, to wyłączenie ekranu. Interfejs Wake Lock API może temu zapobiec.
Pierwszym krokiem jest uzyskanie blokady aktywacji za pomocą funkcji navigator.wakelock.request method()
.
Przekazuję mu ciąg znaków 'screen'
, aby uzyskać blokadę ekranu.
Następnie dodaję detektor zdarzenia, aby otrzymywać powiadomienia o zwolnieniu blokady aktywacji.
Może się tak zdarzyć np. wtedy, gdy zmieni się widoczność karty.
Jeśli tak się stanie, gdy karta znów będzie widoczna, mogę ponownie uzyskać blokadę aktywacji.
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, więc 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 jest pole wyboru Insomnia, które po zaznaczeniu utrzymuje ekran w stanie czuwania.
Interfejs Idle Detection API
Czasami nawet godzinne wpatrywanie się w ekran nie przynosi rezultatów i nie masz najmniejszego pojęcia, co zrobić z kartką z życzeniami. Interfejs Idle Detection API umożliwia aplikacji wykrywanie czasu bezczynności użytkownika. Jeśli użytkownik przez zbyt długi czas nie wykonuje żadnych czynności, aplikacja wraca do stanu początkowego i wyczyści płótno. Ten interfejs API jest obecnie dostępny tylko po udzieleniu uprawnień powiadomień, ponieważ wiele przypadków użycia wykrywania nieaktywności w produkcji jest związanych z powiadomieniami, na przykład wysyłaniem powiadomień tylko na urządzenie, którego użytkownik aktualnie używa.
Po upewnieniu się, że uprawnienia do powiadomień zostały przyznane, tworzymy instancję detektora bezczynności. Rejestruję odbiornik zdarzeń, który wykrywa zmiany stanu bezczynności, w tym stan użytkownika i ekranu. Użytkownik może być aktywny lub nieaktywny, a jego ekran można odblokować lub zablokować. Jeśli użytkownik jest nieaktywny, płótno się czyści. Detektor bezczynności ma próg 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 puste pole Ephemeral (Efemerydalność) jest zaznaczone, a użytkownik zbyt długo nie wykonuje żadnych czynności.
Zakończenie
Uff, co za przejażdżka. Tyle interfejsów API w jednej przykładowej aplikacji. Pamiętaj, że nigdy nie zmuszam użytkownika do uiszczenia opłaty za pobranie funkcji, której nie obsługuje jego przeglądarka. Dzięki stopniowemu ulepszaniu mogę mieć pewność, że wczytuje się tylko odpowiedni kod. W przypadku protokołu HTTP/2 żądania są tanie, więc ten schemat powinien dobrze działać w wielu aplikacjach, chociaż w przypadku naprawdę dużych aplikacji warto rozważyć użycie pakietów.
Aplikacja może wyglądać trochę inaczej w każdej przeglądarce, ponieważ nie wszystkie platformy obsługują wszystkie funkcje, ale jej główna funkcjonalność jest zawsze dostępna – stopniowo ulepszana zgodnie z możliwościami konkretnej przeglądarki. Pamiętaj, że te funkcje mogą się zmieniać nawet w tym samym przeglądarce w zależności od tego, czy aplikacja działa jako zainstalowana aplikacja czy na karcie przeglądarki.
Jeśli interesuje Cię aplikacja Fugu Greetings, znajdź ją i rozwiń na GitHubie.
Zespół Chromium ciężko pracuje nad tym, aby interfejsy Fugu API były jeszcze lepsze. Stosując stopniowe ulepszenia podczas tworzenia mojej aplikacji, dbam o to, aby wszyscy mogli korzystać z niej w dobrej i wartości bazowej. Z kolei użytkownicy korzystający z przeglądarek obsługujących więcej interfejsów API platformy internetowej mają jeszcze lepsze wrażenia. Z niecierpliwością czekam na to, jak wykorzystasz stopniowe ulepszanie w swoich aplikacjach.
Podziękowania
Dziękuję Christianowi Liebelowi i Hemanthowi HM za ich wkład w projekt Fugu Greetings.
Ten artykuł został sprawdzony przez Joe Medley i Kayce Basques.
Jake Archibald pomógł mi znaleźć sytuację dzięki dynamicznemu import()
w kontekście mechanizmu Service Worker.