Migliorare progressivamente la tua app web progressiva

Sviluppare per i browser moderni e migliorare progressivamente come nel 2003

Nel marzo del 2003, Nick Finck e Steve Champeon ha sbalordito il mondo del web design con il concetto di miglioramento progressivo, una strategia per il web design che enfatizzi il caricamento dei contenuti principali della pagina web, che poi aggiunge progressivamente oltre ai contenuti, con livelli di presentazione e funzionalità rigorosi dal punto di vista tecnico. Nel 2003, il miglioramento progressivo riguardava l'uso, all'epoca, delle moderne Funzionalità CSS, JavaScript non invasivo e persino solo Scalable Vector Graphics. Il miglioramento progressivo, nel 2020 e oltre, riguarda l'utilizzo funzionalità del browser moderne.

Web design inclusivo per il futuro con miglioramento progressivo. Diapositiva del titolo della presentazione originale di Finck e Champeon.
. Slide: Web design inclusivo per il futuro con miglioramento progressivo. (Fonte)

JavaScript moderno

A proposito di JavaScript, la situazione del supporto browser per l'ultima versione di JavaScript principale di ES 2015 è fantastico. Il nuovo standard include promesse, moduli, classi, valori letterali modello, funzioni freccia, let e const, parametri predefiniti, generatori, assegnazione destrutturante, resto e distribuzione, Map/Set, WeakMap/WeakSet e molti altri. Sono tutti supportati.

La tabella di supporto CanIUse per le funzionalità ES6 che mostra il supporto in tutti i principali browser.
. La tabella relativa al supporto del browser ECMAScript 2015 (ES6). (Fonte)

Le funzioni asincrone, una funzionalità di ES 2017 e una delle mie preferite possono essere usati in tutti i principali browser. Le parole chiave async e await consentono un comportamento asincrono basato sulla promessa per essere scritte in modo più chiaro, evitando la necessità di configurare esplicitamente le catene di promesse.

La tabella di supporto di CanIUse per le funzioni asincrone che mostra il supporto in tutti i principali browser.
. La tabella relativa al supporto del browser delle funzioni asincrone. (Fonte)

Anche le più recenti aggiunte di lingue a ES 2020, come concatenamento facoltativo e coalescenza null hanno contattato l'assistenza molto rapidamente. Di seguito è riportato un esempio di codice. Per quanto riguarda le funzionalità JavaScript principali, l'erba non potrebbe essere molto più verde di così. è oggi.

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
Immagine di sfondo iconica di Windows XP verde erba.
Il prato è verde per quanto riguarda le funzionalità JavaScript di base. (Screenshot del prodotto Microsoft, utilizzato con autorizzazione.)

App di esempio: Fugu Greetings

Per questo articolo lavoro con una semplice PWA, Saluti Fugu (GitHub). Il nome di questa app è una punta del cappello per Project Fugu 🐡, uno sforzo per dare a tutto il web tutta la potenza delle applicazioni per Android/iOS/desktop. Puoi scoprire di più sul progetto nella relativa pagina di destinazione.

Fugu Greetings è un'app di disegno che ti consente di creare cartoline di auguri virtuali e inviare a chi ami. Un esempio Concetti fondamentali di PWA. È affidabile e completamente offline, quindi anche se non se disponi di una rete, puoi comunque utilizzarla. È inoltre installabile alla schermata Home del dispositivo e si integra perfettamente con il sistema operativo. come applicazione autonoma.

Fugu saluta PWA con un disegno che ricorda il logo della community PWA.
. L'app di esempio Fugu Greetings.

Miglioramento progressivo

Dopodiché, è il momento di parlare del miglioramento progressivo. Il Glossario di MDN Web Docs definisce il concetto come segue:

Il miglioramento progressivo è una filosofia di progettazione che fornisce una base di funzionalità e contenuti essenziali per il maggior numero di utenti possibile, mentre offrendo la migliore esperienza possibile solo agli utenti browser in grado di eseguire tutto il codice richiesto.

Rilevamento delle funzionalità viene generalmente utilizzato per determinare se i browser sono in grado di gestire funzionalità più moderne, mentre i polyfill vengono spesso utilizzati per aggiungere funzionalità mancanti con JavaScript.

[…]

Il miglioramento progressivo è una tecnica utile che consente agli sviluppatori web di concentrarsi sullo sviluppo dei migliori siti web possibili e sul loro funzionamento su più user agent sconosciuti. Riduzione graduale è correlata, ma non è la stessa cosa e viene spesso vista come una direzione opposta a un miglioramento progressivo. In realtà, entrambi gli approcci sono validi e spesso possono completarsi a vicenda.

Collaboratori MDN

Iniziare ogni biglietto di auguri da zero può essere molto complicato. Perché non disporre di una funzionalità che consenta agli utenti di importare un'immagine e iniziare da lì? Con un approccio tradizionale, avresti utilizzato una <input type=file> per ottenere questo risultato. Innanzitutto, devi creare l'elemento, impostarne type su 'file' e aggiungere tipi MIME alla proprietà accept, e poi fare clic in modo programmatico e ascoltare le modifiche. L'immagine selezionata viene importata direttamente sul canvas.

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

Se è presente una funzionalità di importazione, probabilmente dovrebbe essere presente una funzionalità di esportazione. per consentire agli utenti di salvare le loro cartoline di auguri localmente. Il modo tradizionale di salvare i file è creare un link di ancoraggio con una download con un URL del blob come href. Devi anche "fare clic" in modo programmatico per attivare il download, e, per evitare fughe di memoria, non dimenticare di revocare l'URL dell'oggetto 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();
};

Ma aspetta un minuto. Mentalmente non hai "scaricato" una cartolina di auguri, hai "salvato" li annotino. Invece di mostrare un "salvataggio" che ti consente di scegliere dove posizionare il file, Il browser abbia scaricato direttamente la cartolina di auguri senza interazione dell'utente e lo ha inserito direttamente nella cartella Download. Non è male.

E se ci fosse un modo migliore? E se potessi semplicemente aprire un file locale, modificarlo e salvare le modifiche, in un nuovo file o tornare al file originale che hai aperto inizialmente? Si scopre che c'è. L'API File System Access ti consente di aprire e creare file e le modifiche e il salvataggio .

Quindi, come faccio a rilevare le caratteristiche di un'API? L'API File System Access espone un nuovo metodo window.chooseFileSystemEntries(). Di conseguenza, devo caricare in modo condizionale diversi moduli di importazione ed esportazione a seconda che questo metodo sia disponibile o meno. Di seguito è mostrato come fare.

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'),
    ]);
  }
};

Ma prima di esaminare i dettagli dell'API File System Access, per evidenziare rapidamente il pattern di miglioramento progressivo. Sui browser che attualmente non supportano l'API File System Access, carico gli script precedenti. Di seguito sono riportate le schede di rete di Firefox e Safari.

Safari Web Inspector mostra i file legacy che vengono caricati.
. Scheda Rete di Safari Web Inspector.
di Gemini Advanced.
.
. Strumenti per sviluppatori di Firefox in cui vengono visualizzati i file legacy che vengono caricati.
Scheda di rete degli Strumenti per sviluppatori di Firefox.

Tuttavia, su Chrome, un browser che supporta l'API, vengono caricati solo i nuovi script. Ciò è reso possibile con eleganza dinamico import(), che tutti i browser moderni assistenza. Come dicevo prima, l'erba è piuttosto verde in questi giorni.

Chrome DevTools che mostra i file moderni che vengono caricati.
. Scheda di rete di Chrome DevTools.

API File System Access

Ora che abbiamo risolto il problema, è il momento di esaminare l'implementazione effettiva basata sull'API File System Access. Per importare un'immagine, chiamo window.chooseFileSystemEntries() e la passiamo a una proprietà accepts in cui dico di volere i file immagine. Sono supportate sia le estensioni di file che i tipi MIME. Ne deriva un handle di file, da cui posso recuperare il file effettivo chiamando 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);
  }
};

Esportare un'immagine è quasi la stessa, ma questa volta Devo passare un parametro di tipo 'save-file' al metodo chooseFileSystemEntries(). Viene visualizzata una finestra di dialogo per il salvataggio dei file. Con il file aperto, questa operazione non era necessaria perché l'impostazione predefinita è 'open-file'. Ho impostato il parametro accepts in modo simile a prima, ma questa volta è limitato solo alle immagini PNG. Ricevo nuovamente l'handle di un file, ma anziché recuperare il file, questa volta creo uno stream scrivibile chiamando createWritable(). Poi scrivo il blob, che è l'immagine della mia cartolina di auguri, nel file. Infine, chiudo lo stream scrivibile.

Può sempre verificarsi un errore: lo spazio sul disco potrebbe essere esaurito, potrebbe esserci un errore di scrittura o lettura oppure potrebbe semplicemente che l'utente annullasse la finestra di dialogo del file. Questo è il motivo per cui racchiudo sempre le chiamate in un'istruzione 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);
  }
};

Grazie al miglioramento progressivo con l'API File System Access, Posso aprire un file come prima. Il file importato viene disegnato direttamente sul canvas. Posso apportare le modifiche e infine salvarle con una finestra di dialogo di salvataggio reale dove posso scegliere il nome e la posizione di archiviazione del file. Ora il file è pronto per essere conservato per l'eternità.

App Fugu Greetings con una finestra di dialogo che apre i file.
. La finestra di dialogo di apertura del file.
di Gemini Advanced.
.
. App Fugu Greetings ora con un&#39;immagine importata.
L'immagine importata.
di Gemini Advanced.
.
. App Fugu Greetings con l&#39;immagine modificata.
Salvataggio dell'immagine modificata in un nuovo file.

API Web Share e Web Share Target

A parte l'archiviazione per l'eternità, forse voglio anche condividere la mia cartolina di auguri. Si tratta di un aspetto che l'API Web Share e L'API Web Share Target mi consente di farlo. I sistemi operativi mobile e più recenti per computer hanno acquisito la condivisione integrata i meccanismi della ricerca di informazioni. Ad esempio, di seguito è riportato il foglio di condivisione di Safari su computer desktop su macOS attivato da un articolo su nel mio blog. Quando fai clic sul pulsante Condividi articolo, puoi condividere un link dell'articolo con un amico, per ad esempio tramite l'app Messaggi di macOS.

Foglio di condivisione di Safari su computer desktop su macOS attivato dal pulsante Condividi di un articolo
. API Web Share su Safari su macOS.

Il codice da utilizzare per crearlo è piuttosto semplice. Chiamo navigator.share() e passagli title, text e url facoltativi in un oggetto. E se volessi allegare un'immagine? Il livello 1 dell'API Web Share non supporta ancora questa funzionalità. La buona notizia è che Condivisione web di livello 2 ha aggiunto funzionalità di condivisione file.

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

Lascia che ti mostri come far funzionare tutto con l'applicazione per il biglietto di auguri Fugu. Innanzitutto, devo preparare un oggetto data con un array files composto da un blob, quindi un title e un text. Come best practice, poi utilizzo il nuovo metodo navigator.canShare(), che cosa suggerisce il nome: Mi dice se l'oggetto data che sto cercando di condividere può essere tecnicamente condiviso dal browser. Se navigator.canShare() mi dice che i dati possono essere condivisi, sono pronto per chiama navigator.share() come prima. Dato che può verificarsi un errore, sto usando di nuovo un blocco 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);
  }
};

Come prima, utilizzo il miglioramento progressivo. Se 'share' e 'canShare' esistono nell'oggetto navigator, solo allora vado avanti e carica share.mjs tramite import() dinamico. Nei browser come Safari per dispositivi mobili che soddisfano solo una delle due condizioni, non carico la funzionalità.

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

In Fugu Greetings, se tocco il pulsante Condividi in un browser che supporta Chrome su Android, si apre il foglio condiviso integrato. Ad esempio posso scegliere Gmail e il widget del compositore di email si apre con la immagine allegata.

Foglio di condivisione a livello di sistema operativo che mostra varie app con cui condividere l&#39;immagine.
. È in corso la scelta dell'app con cui condividere il file.
di Gemini Advanced.
.
. Widget di scrittura delle email di Gmail con l&#39;immagine allegata.
Il file viene allegato a una nuova email nel compositore di Gmail.

API Contact Selecter

Voglio parlare dei contatti, ovvero della rubrica di un dispositivo o l'app di gestione dei contatti. Quando scrivi una cartolina di auguri, potrebbe non essere sempre facile scrivere correttamente il nome di qualcuno. Ad esempio, ho un amico Sergio che preferisce che il suo nome si scriva con lettere cirilliche. Sono usando una tastiera QWERTZ tedesca e non hanno idea di come digitare il nome. Si tratta di un problema che l'API Contact Selecter può risolvere. Dato che un mio amico è memorizzato nell'app Contatti del mio telefono, tramite l'API Contacts selettore, posso accedere ai miei contatti dal web.

Innanzitutto, devo specificare l'elenco di proprietà a cui voglio accedere. In questo caso, voglio solo i nomi, ma per altri casi d'uso potrei essere interessato a numeri di telefono, email, avatar icone o indirizzi fisici. Poi configuro un oggetto options e imposto multiple su true, in modo da poter selezionare più di una voce. Infine, posso chiamare navigator.contacts.select(), che restituisce le proprietà desiderate per i contatti selezionati dall'utente.

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

E a questo punto probabilmente hai già imparato lo schema: Carico il file solo quando l'API è effettivamente supportata.

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

Nel saluto di Fugu, quando tocco il pulsante Contatti e seleziono i miei due amici migliori, Cер Rete Display Mamixa©ловttiven Брspinis e 劳伦斯·爱德华·"拉里"·佩奇, puoi vedere come il selettore di contatti può mostrare solo i nomi, ma non gli indirizzi email o altre informazioni come i numeri di telefono. I loro nomi sono poi disegnati sulla mia cartolina di auguri.

Selettore di contatti che mostra i nomi di due contatti nella rubrica.
. Selezione di due nomi con il selettore contatti dalla rubrica.
di Gemini Advanced.
.
. I nomi dei due contatti selezionati in precedenza, disegnati sulla cartolina di auguri.
I due nomi vengono poi disegnati sulla cartolina di auguri.

L'API Asynchronous Clipboard

Nel prossimo passaggio c'è il copia e incolla. Una delle nostre operazioni preferite dagli sviluppatori di software è il copia e incolla. Come autore di biglietti di auguri, a volte mi piacerebbe fare lo stesso. posso incollare un'immagine nella cartolina di auguri su cui sto lavorando, o copia la mia cartolina di auguri per poter continuare a modificarla da qualche altra parte. L'API Async Clipboard, supporta sia testo che immagini. Ti illustrerò come ho aggiunto il supporto per il copia e incolla in Fugu App di benvenuto.

Per copiare qualcosa negli appunti del sistema, devo scrivere sul sistema. Il metodo navigator.clipboard.write() prende un array di elementi Appunti come . Ogni elemento appunti è essenzialmente un oggetto con un blob come valore e il tipo di blob come chiave.

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

Per incollare, devo scorrere gli elementi degli appunti che ottengo chiamando navigator.clipboard.read(). Il motivo è che potrebbero essere presenti più elementi negli appunti rappresentazioni diverse. Ogni elemento degli appunti ha un campo types che mi indica i tipi MIME delle Google Cloud. Chiamo il metodo getType() dell'elemento degli appunti, passando il Tipo MIME ottenuto in precedenza.

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

E ormai è quasi inutile dirlo. Eseguo questa operazione solo sui browser supportati.

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

Come funziona nella pratica? Ho un'immagine aperta nell'app Anteprima macOS e e lo copio negli appunti. Quando faccio clic su Incolla, l'app Fugu Greetings mi chiede se voglio consentire all'app di vedere testo e immagini negli appunti.

App Fugu Greetings che mostra il prompt di autorizzazione per gli appunti.
. Il prompt di autorizzazione per gli appunti.

Infine, dopo aver accettato l'autorizzazione, l'immagine viene incollata nell'applicazione. Anche il contrario funziona. Copia un biglietto di auguri negli appunti. Quando apro Anteprima e faccio clic su File, quindi su Nuovo dagli appunti, la cartolina di auguri viene incollata in una nuova immagine senza titolo.

L&#39;app macOS Preview con un&#39;immagine senza titolo appena incollata.
. Un'immagine incollata nell'app Anteprima macOS.

L'API Badging

Un'altra API utile è l'API Badging. Essendo una PWA installabile, Fugu Greetings ha ovviamente l'icona di un'app che gli utenti possono inserire nel dock delle app o nella schermata Home. Un modo semplice e divertente per dimostrare l'API è (ab)usarla in Fugu Greetings come contatore dei tratto di penna. Ho aggiunto un listener di eventi che incrementa il contatore dei tratti di penna ogni volta che si verifica l'evento pointerdown e poi imposta il badge dell'icona aggiornato. Ogni volta che il canvas viene cancellato, il contatore viene reimpostato e il badge viene rimosso.

let strokes = 0;

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

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

Questa funzionalità è un miglioramento progressivo, pertanto la logica di caricamento è la stessa di consueto.

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

In questo esempio ho disegnato i numeri da uno a sette, utilizzando un tratto di penna per numero. Il contatore del badge sull'icona è ora impostato su sette.

I numeri da uno a sette disegnati sulla cartolina d&#39;auguri, ognuno con un tratto di penna.
. Disegno dei numeri da 1 a 7, usando sette tratti di penna.
di Gemini Advanced.
.
. Icona del badge nell&#39;app Fugu Greetings con il numero 7.
Il contatore dei tratto di penna sotto forma di badge dell'icona dell'app.

API Periodic Background Sync

Vuoi iniziare la giornata con qualcosa di nuovo? Una caratteristica molto interessante dell'app Fugu Greetings è che può ispirarti ogni mattina con una nuova immagine di sfondo per iniziare la tua cartolina di auguri. L'app utilizza l'API Periodic Background Sync per raggiungere questo obiettivo.

Il primo passaggio consiste nel registrare un evento di sincronizzazione periodico nella registrazione del service worker. Rimane in ascolto di un tag di sincronizzazione chiamato 'image-of-the-day' e ha un intervallo minimo di un giorno, in modo che l'utente possa ottenere una nuova immagine di sfondo ogni 24 ore.

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

Il secondo passaggio consiste nell'ascoltare l'evento periodicsync nel service worker. Se il tag evento è 'image-of-the-day', ovvero quello registrato in precedenza, l'immagine del giorno viene recuperata tramite la funzione getImageOfTheDay(), e il risultato viene propagato a tutti i clienti, quindi è possibile aggiornare canvas e .

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

Anche in questo caso si tratta di un miglioramento progressivo, per cui il codice viene caricato solo quando L'API è supportata dal browser. Questo vale sia per il codice client sia per il codice del service worker. Sui browser non supportati, nessuno dei due viene caricato. Nota come nel service worker, invece di uno import() dinamico (non supportato nel contesto di un service worker) ancora), Uso la versione classica 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');
}

In Fugu Greetings, se premi il pulsante Sfondo viene mostrata l'immagine della cartolina di auguri del giorno. che viene aggiornato quotidianamente tramite l'API Periodic Background Sync.

App di auguri Fugu con una nuova immagine di cartoline di auguri del giorno.
. Se premi il pulsante Sfondo, viene visualizzata l'immagine del giorno.

API Notification Triggers

A volte, anche con molta ispirazione, hai bisogno di uno stimolo per finire un saluto iniziato . Questa è una funzionalità abilitata dall'API Notification Triggers. Come utente, posso inserire un'ora in cui voglio essere sollecitato a completare la mia cartolina di auguri. Quando sarà il momento, riceverò una notifica che mi informa che la mia cartolina di auguri è in attesa.

Dopo aver richiesto l'ora target, l'applicazione pianifica la notifica con un showTrigger. Può essere un TimestampTrigger con la data target selezionata in precedenza. La notifica di promemoria verrà attivata localmente: non è necessaria alcuna rete o lato server.

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

Come per tutto ciò che ho mostrato finora, si tratta di un miglioramento progressivo, in modo che il codice venga caricato solo in modo condizionale.

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

Quando seleziono la casella di controllo Promemoria in Fugu Greetings, un prompt chiede quando voglio ricevere un promemoria per completare la mia cartolina di auguri.

App Fugu Greetings con un messaggio che chiede all&#39;utente quando vuole ricevere un promemoria per completare la cartolina di auguri.
. Programmazione di una notifica locale per ricevere un promemoria per completare una cartolina di auguri.

Quando si attiva una notifica programmata in Fugu Greetings, viene visualizzata come qualsiasi altra notifica ma, come ho scritto prima, non era necessaria una connessione di rete.

Centro notifiche macOS che mostra una notifica attivata dai messaggi di benvenuto Fugu.
. La notifica attivata viene visualizzata nel Centro notifiche di macOS.

API Wake Lock

Voglio includere anche l'API Wake Lock. A volte basta fissare abbastanza a lungo lo schermo fino all'ispirazione ti bacia. In tal caso, il peggio che può succedere è che lo schermo si disattivi. L'API Wake Lock può impedire che ciò accada.

Il primo passo è ottenere un wakelock con il navigator.wakelock.request method(). Passo la stringa 'screen' per ottenere un wakelock dello schermo. Aggiungo quindi un listener di eventi per essere informato quando viene rilasciato il wakelock. Questo può accadere, ad esempio, quando la visibilità della scheda cambia. In questo caso, quando la scheda diventa di nuovo visibile, posso ottenere di nuovo il wakelock.

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

Sì, si tratta di un miglioramento progressivo, quindi devo caricarlo solo quando il browser e supporta l'API.

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

In Fugu Greetings, è presente una casella di controllo Insomnia che, se selezionata, mantiene l'attivazione dello schermo.

La casella di controllo &quot;insonnia&quot;, se selezionata, mantiene lo schermo attivo.
. La casella di controllo Insomnia mantiene attiva l'app.

API Idle Detection

A volte, anche se guardi lo schermo per ore, è semplicemente inutile e non ti viene in mente nemmeno un'idea di cosa fare con la tua cartolina di auguri. L'API Idle Detection consente all'app di rilevare il tempo di inattività dell'utente. Se l'utente rimane inattivo per troppo tempo, l'app viene reimpostata allo stato iniziale e cancella il canvas. Questa API è attualmente protetta autorizzazione alle notifiche, poiché molti casi d'uso in produzione di rilevamento di inattività sono correlati alle notifiche, ad esempio per inviare una notifica soltanto a un dispositivo che l'utente sta utilizzando attivamente.

Dopo aver verificato che l'autorizzazione alle notifiche sia stata concessa, creo un'istanza del rilevatore di inattività. Registro un listener di eventi che rimane in ascolto delle modifiche inattive, che include l'utente e lo stato della schermata. L'utente può essere attivo o inattivo, e lo schermo può essere sbloccato o bloccato. Se l'utente è inattivo, la tela viene cancellata. Imposto al rilevatore di inattività una soglia di 60 secondi.

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

Come sempre, carico questo codice solo quando il browser lo supporta.

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

Nell'app Fugu Greetings, la tela si cancella quando la casella di controllo Temporaneo è selezionata e l'utente è rimasto inattivo per troppo tempo.

App Fugu Greetings con un canvas cancellato dopo che l&#39;utente è rimasto inattivo per troppo tempo.
. Quando la casella di controllo Temporanea è selezionata e l'utente è rimasto inattivo per troppo tempo, il canvas viene cancellato.

Chiusura

Fiuuu, che giro. Tantissime API in un'unica app di esempio. Ricorda che non faccio mai pagare all'utente il costo del download per una funzionalità non supportata dal browser. Grazie al miglioramento progressivo, mi assicuro che venga caricato solo il codice pertinente. E poiché con HTTP/2 le richieste sono economiche, questo pattern dovrebbe funzionare bene per molte applicazioni, anche se potresti prendere in considerazione un bundler per le app molto grandi.

Riquadro Network (Rete) di Chrome DevTools che mostra solo le richieste di file con codice supportato dal browser corrente.
. La scheda Network (Rete) di Chrome DevTools mostra solo le richieste di file con codice supportato dal browser corrente.

L'app potrebbe avere un aspetto diverso su ogni browser poiché non tutte le piattaforme supportano tutte le funzionalità. ma la funzionalità di base è sempre presente: progressivamente migliorate in base alle capacità del browser specifico. Tieni presente che queste funzionalità possono cambiare anche nello stesso browser. a seconda che l'app sia in esecuzione come app installata o in una scheda del browser.

Fugu Saluti in esecuzione su Chrome Android, che mostra molte funzionalità disponibili.
. Saluti Fugu in esecuzione su Chrome per Android.
di Gemini Advanced.
.
. Saluti Fugu in esecuzione su Safari desktop, con un numero minore di funzionalità disponibili.
Saluti Fugu in esecuzione su Safari su computer.
di Gemini Advanced.
.
. Fugu Saluti in esecuzione su Chrome per computer desktop, con molte funzionalità disponibili.
Fugu Greetings in esecuzione su Chrome per computer.

Se ti interessa l'app Fugu Greetings, cercalo e crea un fork su GitHub.

Repo di Fugu Greetings su GitHub.
. l'app Fugu Greetings su GitHub.

Il team di Chromium sta lavorando duramente per rendere l'erba più verde quando si tratta di API Fugu avanzate. Applicando miglioramenti progressivi nello sviluppo della mia app, Mi assicuro che tutti usufruiscano di un'esperienza di base positiva e ma che le persone che utilizzano browser che supportano più API delle piattaforme web usufruiscono di un'esperienza ancora migliore. Non vedo l'ora di scoprire cosa farai con il miglioramento progressivo delle tue app.

Ringraziamenti

Sono grato a Christian Liebel e Hemanth HM che hanno entrambi contribuito a Fugu Greetings. Questo articolo è stato esaminato da Joe Medley e Kayce Basques. Jake Archibald mi ha aiutato a scoprire la situazione con import() dinamico in un contesto di service worker.