Applicazione dei principi di programmazione delle mini app a un progetto di esempio

Il dominio dell'app

Per mostrare il modo di programmare delle mini app applicato a un'app web, avevo bisogno di un'idea per un'app piccola ma sufficientemente completa. L'allenamento a intervalli ad alta intensità (HIIT) è una strategia di esercizio cardiovascolare che prevede l'alternanza di serie di brevi periodi di esercizio anaerobico intenso con periodi di recupero meno intensi. Molti allenamenti HIIT utilizzano timer HIIT, ad esempio questa sessione online di 30 minuti dal canale YouTube The Body Coach TV.

Sessione di allenamento HIIT online con timer verde ad alta intensità.
Periodo attivo.
Sessione di allenamento HIIT online con timer rosso a bassa intensità.
Periodo di riposo.

App di esempio HIIT Time

Per questo capitolo, ho creato un esempio di base di un'applicazione di questo tipo, chiamata "HIIT Time", che consente all'utente di definire e gestire vari timer, sempre costituiti da un intervallo di alta e bassa intensità, e poi di selezionarne uno per una sessione di allenamento. È un'app responsive con una barra di navigazione, una barra delle schede e tre pagine:

  • Allenamento: la pagina attiva durante un allenamento. Consente all'utente di selezionare uno dei timer e presenta tre anelli di avanzamento: il numero di serie, il periodo attivo e il periodo di riposo.
  • Timer: gestisce i timer esistenti e consente all'utente di crearne di nuovi.
  • Preferenze: consente di attivare/disattivare gli effetti sonori e la sintesi vocale e di selezionare la lingua e il tema.

Gli screenshot seguenti danno un'idea dell'applicazione.

App di esempio HIIT Time in modalità verticale.
Scheda "Allenamento" di HIIT Time in modalità verticale.
App di esempio HIIT Time in modalità orizzontale.
Scheda "Allenamento" di HIIT Time in modalità Orizzontale.
App di esempio HIIT Time che mostra la gestione di un timer.
Gestione dei timer di HIIT Time.

Struttura dell'app

Come indicato sopra, l'app è composta da una barra di navigazione, una barra delle schede e tre pagine, disposte in una griglia. La barra di navigazione e la barra delle schede sono realizzate come iframe con un <div> contenitore tra di loro con altri tre iframe per le pagine, di cui uno è sempre visibile e dipende dalla selezione attiva nella barra delle schede. Un iframe finale che punta a about:blank serve per le pagine in-app create dinamicamente, necessarie per modificare i timer esistenti o crearne di nuovi. Chiamo questo pattern app a pagina singola multipagina (MPSPA).

Visualizzazione di Chrome DevTools della struttura HTML dell&#39;app che mostra che è composta da sei iframe: uno per la barra di navigazione, uno per la barra delle schede e tre raggruppati per ogni pagina dell&#39;app, con un iframe segnaposto finale per le pagine dinamiche.
L'app è composta da sei iframe.

Markup lit-html basato su componenti

La struttura di ogni pagina è realizzata come scaffold lit-html che viene valutato dinamicamente in fase di runtime. Per un background su lit-html, si tratta di una libreria di modelli HTML efficiente, espressiva ed estensibile per JavaScript. Utilizzandola direttamente nei file HTML, il modello di programmazione mentale è direttamente orientato all'output. Come programmatore, scrivi un modello di come sarà l'output finale e lit-html riempie dinamicamente gli spazi in base ai tuoi dati e collega i listener di eventi. L'app utilizza elementi personalizzati di terze parti come Shoelace's <sl-progress-ring> o un elemento personalizzato autoimplementato chiamato <human-duration>. Poiché gli elementi personalizzati hanno un'API dichiarativa (ad esempio, l'attributo percentage dell'anello di progresso), funzionano bene con lit-html, come puoi vedere nell'elenco di seguito.

<div>
  <button class="start" @click="${eventHandlers.start}" type="button">
    ${strings.START}
  </button>
  <button class="pause" @click="${eventHandlers.pause}" type="button">
    ${strings.PAUSE}
  </button>
  <button class="reset" @click="${eventHandlers.reset}" type="button">
    ${strings.RESET}
  </button>
</div>

<div class="progress-rings">
  <sl-progress-ring
    class="sets"
    percentage="${Math.floor(data.sets/data.activeTimer.sets*100)}"
  >
    <div class="progress-ring-caption">
      <span>${strings.SETS}</span>
      <span>${data.sets}</span>
    </div>
  </sl-progress-ring>
</div>
Tre pulsanti e un anello di avanzamento.
Sezione della pagina sottoposta a rendering corrispondente al markup sopra.

Modello di programmazione

Ogni pagina ha una classe Page corrispondente che riempie il markup lit-html con la vita fornendo implementazioni dei gestori di eventi e fornendo i dati per ogni pagina. Questa classe supporta anche metodi del ciclo di vita come onShow(), onHide(), onLoad() e onUnload(). Le pagine hanno accesso a un datastore che serve per condividere lo stato per pagina e lo stato globale, se necessario. Tutte le stringhe sono gestite centralmente, quindi l'internazionalizzazione è integrata. Il routing viene gestito dal browser in modo essenzialmente senza costi, poiché l'unica cosa che fa l'app è attivare/disattivare la visibilità dell'iframe e, per le pagine create dinamicamente, modificare l'attributo src dell'iframe segnaposto. L'esempio seguente mostra il codice per chiudere una pagina creata dinamicamente.

import Page from '../page.js';

const page = new Page({
  eventHandlers: {
    back: (e) => {
      e.preventDefault();
      window.top.history.back();
    },
  },
});
Pagina in-app realizzata come iframe.
La navigazione avviene da iframe a iframe.

Stili

Lo stile delle pagine avviene per pagina nel proprio file CSS con ambito. Ciò significa che in genere gli elementi possono essere indirizzati direttamente dai nomi degli elementi, poiché non possono verificarsi conflitti con altre pagine. Gli stili globali vengono aggiunti a ogni pagina, quindi le impostazioni centrali come font-family o box-sizing non devono essere dichiarate ripetutamente. È qui che vengono definiti anche i temi e le opzioni della modalità Buio. L'elenco seguente mostra le regole per la pagina Preferenze che dispone i vari elementi del modulo in una griglia.

main {
  max-width: 600px;
}

form {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-gap: 0.5rem;
  margin-block-end: 1rem;
}

label {
  text-align: end;
  grid-column: 1 / 2;
}

input,
select {
  grid-column: 2 / 3;
}
Pagina delle preferenze dell&#39;app HIIT Time che mostra un modulo nel layout a griglia.
Ogni pagina è un mondo a sé. Lo stile avviene direttamente con i nomi degli elementi.

Wakelock dello schermo

Durante un allenamento, lo schermo non deve spegnersi. Sui browser che lo supportano, HIIT Time lo realizza tramite un wakelock dello schermo. Lo snippet seguente mostra come viene eseguito.

if ('wakeLock' in navigator) {
  const requestWakeLock = async () => {
    try {
      page.shared.wakeLock = await navigator.wakeLock.request('screen');
      page.shared.wakeLock.addEventListener('release', () => {
        // Nothing.
      });
    } catch (err) {
      console.error(`${err.name}, ${err.message}`);
    }
  };
  // Request a screen wake lock…
  await requestWakeLock();
  // …and re-request it when the page becomes visible.
  document.addEventListener('visibilitychange', async () => {
    if (
      page.shared.wakeLock !== null &&
      document.visibilityState === 'visible'
    ) {
      await requestWakeLock();
    }
  });
}

Test dell'applicazione

L'applicazione HIIT Time è disponibile su GitHub. Puoi provare la demo in una nuova finestra, o direttamente nell'incorporamento iframe di seguito, che simula un dispositivo mobile.

Ringraziamenti

Questo articolo è stato rivisto da Joe Medley, Kayce Basques, Milica Mihajlija, Alan Kent, e Keith Gu.