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