Die App-Domain
Um zu zeigen, wie eine Mini-App programmiert werden kann, brauchte ich eine kleine, aber vollständige App-Idee. Hochintensives Intervalltraining (HIIT) ist eine Strategie für kardiovaskuläre Trainings mit abwechselnden, kurzen, intensiven anaeroben Trainings mit weniger intensiven Erholungsphasen. In vielen HIIT-Trainings werden HIIT-Timer verwendet, z. B. diese 30-minütige Online-Sitzung vom YouTube-Kanal The Body Coach TV.
![Online-Trainingseinheit beim HIIT-Training mit grünem Timer mit hoher Intensität.](https://web.dev/static/articles/mini-apps/mini-app-example-project/image/hiit-training-online-sess-ed5b035be1093.png?authuser=6&hl=de)
![HIIT-Training von Online-Trainingseinheit mit rotem Timer mit niedriger Intensität.](https://web.dev/static/articles/mini-apps/mini-app-example-project/image/hiit-training-online-sess-a0dd5340756f1.png?authuser=6&hl=de)
Beispiel-App für HIIT-Zeit
Für dieses Kapitel habe ich ein einfaches Beispiel für eine solche HIIT-Timer-Anwendung mit dem Namen „HIIT Time“ erstellt, mit dem Nutzer verschiedene Timer definieren und verwalten können, die immer aus einem Intervall mit hoher und niedriger Intensität bestehen, und dann einen davon für eine Trainingseinheit auswählen. Es ist eine responsive App mit einer Navigationsleiste, einer Tableiste und drei Seiten:
- Workout:Die aktive Seite während eines Trainings. Der Nutzer kann einen der Timer auswählen. Es werden drei Fortschrittsanzeigen angezeigt: die Anzahl der Sätze, die aktive Periode und die Ruhephase.
- Timer:Verwaltet vorhandene Timer und ermöglicht dem Nutzer, neue Timer zu erstellen.
- Einstellungen:Hier können Sie Soundeffekte und die Sprachausgabe umschalten sowie Sprache und Design auswählen.
Die folgenden Screenshots zeigen einen Eindruck der App.
App-Struktur
Wie oben beschrieben, besteht die App aus einer Navigationsleiste, einer Tableiste und drei Seiten, die in einem Raster angeordnet sind.
Die Navigationsleiste und die Tableiste werden als iFrames mit einem <div>
-Container dazwischen realisiert. Dazwischen gibt es drei weitere iFrames für die Seiten, von denen einer immer sichtbar ist und von der aktiven Auswahl in der Tableiste abhängig ist.
Ein abschließender iFrame, der auf about:blank
verweist, dient für dynamisch erstellte In-App-Seiten, die zum Ändern vorhandener Timer oder zum Erstellen neuer Timer erforderlich sind.
Ich nenne dieses Muster MSPA (Multi-Page Single-Page App).
![In der Ansicht mit den Chrome-Entwicklertools ist die HTML-Struktur der App zu sehen. Es besteht aus sechs iFrames: einem für die Navigationsleiste, einem für die Tableiste und drei gruppierten iFrames für jede Seite der App sowie einem abschließenden Platzhalter-iFrame für dynamische Seiten.](https://web.dev/static/articles/mini-apps/mini-app-example-project/image/chrome-devtools-view-the-9da043317ba5b.png?authuser=6&hl=de)
Komponentenbasiertes Lit-HTML-Markup
Die Struktur jeder Seite wird als lit-html-Gerüst realisiert, das während der Laufzeit dynamisch ausgewertet wird.
Als Hintergrund für Lit-HTML gilt: Es ist eine effiziente, ausdrucksstarke, erweiterbare HTML-Vorlagenbibliothek für JavaScript.
Durch die direkte Verwendung in den HTML-Dateien ist das mentale Programmiermodell direkt ausgabeorientiert.
Als Programmierer schreiben Sie eine Vorlage, die zeigt, wie die Endausgabe aussehen wird. Anschließend füllt Lit-HTML die Lücken basierend auf Ihren Daten dynamisch und schließt die Ereignis-Listener ein.
Die App verwendet benutzerdefinierte Elemente von Drittanbietern wie <sl-progress-ring>
von Shoelace oder ein selbst implementiertes benutzerdefiniertes Element namens <human-duration>
.
Da benutzerdefinierte Elemente eine deklarative API haben (z. B. das Attribut percentage
des Fortschrittsrings), arbeiten sie gut mit Lit-HTML zusammen, wie Sie in der folgenden Liste sehen können.
<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>
![Drei Tasten und ein Fortschrittsring.](https://web.dev/static/articles/mini-apps/mini-app-example-project/image/three-buttons-a-progress-df2f6a20c093.png?authuser=6&hl=de)
Programmiermodell
Jede Seite hat eine entsprechende Page
-Klasse, die das Lit-HTML-Markup mit Leben füllt, indem sie Implementierungen der Event-Handler und die Daten für jede Seite bereitstellt.
Diese Klasse unterstützt auch Lebenszyklusmethoden wie onShow()
, onHide()
, onLoad()
und onUnload()
.
Seiten haben Zugriff auf einen Datenspeicher, der für die Freigabe des optionalen beibehaltenen Status pro Seite und des globalen Status dient.
Alle Strings werden zentral verwaltet, sodass die Internationalisierung integriert ist.
Das Routing wird vom Browser grundsätzlich kostenlos abgewickelt, da die App lediglich die iFrame-Sichtbarkeit ein- und ausschaltet und bei dynamisch erstellten Seiten das src
-Attribut des Platzhalter-iFrames ändert.
Das folgende Beispiel zeigt den Code zum Schließen einer dynamisch erstellten Seite.
import Page from '../page.js';
const page = new Page({
eventHandlers: {
back: (e) => {
e.preventDefault();
window.top.history.back();
},
},
});
![In-App-Seite wurde als iFrame implementiert.](https://web.dev/static/articles/mini-apps/mini-app-example-project/image/in-app-page-realized-an-25ec73e15af7f.png?authuser=6&hl=de)
Stile
Der Stil von Seiten erfolgt pro Seite in einer eigenen CSS-Datei mit einem Geltungsbereich.
Elemente können also in der Regel einfach direkt über ihre Elementnamen angesprochen werden, da keine Konflikte mit anderen Seiten auftreten können.
Globale Stile werden jeder Seite hinzugefügt, sodass zentrale Einstellungen wie font-family
oder box-sizing
nicht wiederholt deklariert werden müssen.
Hier werden auch die Optionen für Designs und den dunklen Modus definiert.
In der folgenden Liste sind die Regeln für die Seite „Einstellungen“ aufgeführt, auf der die verschiedenen Formularelemente in einem Raster angeordnet sind.
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;
}
![Seite mit den Einstellungen der HIIT-Zeit-App mit einem Formular im Rasterlayout.](https://web.dev/static/articles/mini-apps/mini-app-example-project/image/hiit-app-preferences-pag-3d83359174159.png?authuser=6&hl=de)
Display-Wakelock
Während des Trainings sollte sich der Bildschirm nicht ausschalten. In Browsern, die dies unterstützen, erkennt HIIT-Zeit dies über einen Display-Wakelock. Das folgende Snippet zeigt, wie das funktioniert.
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();
}
});
}
Anwendung testen
Die HIIT-Zeitanwendung ist auf GitHub verfügbar. Du kannst die Demo in einem neuen Fenster oder direkt in der folgenden iFrame-Einbettung ausprobieren, die ein Mobilgerät simuliert.
Danksagungen
Dieser Artikel wurde von Joe Medley, Kayce Basques, Milica Mihajlija, Alan Kent und Keith Gu verfasst.