El dominio de la app
Para mostrar la forma de programar de las apps pequeñas aplicada a una app web, necesitaba una idea de app pequeña, pero lo suficientemente completa. El entrenamiento de intervalos de alta intensidad (HIIT) es una estrategia de ejercicio cardiovascular que consiste en alternar series de períodos cortos de ejercicio anaeróbico intenso con períodos de recuperación menos intensos. Muchos entrenamientos de HIIT usan cronómetros de HIIT, como esta sesión en línea de 30 minutos del canal de YouTube The Body Coach TV.
App de ejemplo de HIIT Time
Para este capítulo, creé un ejemplo básico de una aplicación de temporizador de HIIT con el nombre apropiado “HIIT Time”, que permite al usuario definir y administrar varios temporizadores, que siempre consisten en un intervalo de alta y baja intensidad, y, luego, seleccionar uno de ellos para una sesión de entrenamiento. Es una app responsiva con una barra de navegación, una barra de pestañas y tres páginas:
- Entrenamiento: Es la página activa durante un entrenamiento. Permite que el usuario seleccione uno de los cronómetros y cuenta con tres anillos de progreso: la cantidad de series, el período activo y el período de descanso.
- Cronómetros: Administra los cronómetros existentes y permite que el usuario cree otros nuevos.
- Preferences: Permite activar o desactivar los efectos de sonido y la salida de voz, así como seleccionar el idioma y el tema.
En las siguientes capturas de pantalla, se muestra una impresión de la aplicación.
Estructura de app
Como se describió anteriormente, la app consta de una barra de navegación, una barra de pestañas y tres páginas, organizadas en una cuadrícula.
La barra de navegación y la barra de pestañas se crean como iframes con un contenedor <div>
entre ellos y tres iframes más para las páginas, de los cuales uno siempre está visible y depende de la selección activa en la barra de pestañas.
Un iframe final que apunta a about:blank
se usa para las páginas integradas en la app creadas de forma dinámica, que son necesarias para modificar temporizadores existentes o crear otros nuevos.
A este patrón lo llamo app de una sola página de varias páginas (MPSPA).
Lenguaje de marcado lit-html basado en componentes
La estructura de cada página se realiza como andamiaje lit-html que se evalúa de forma dinámica durante el tiempo de ejecución.
Para obtener información sobre lit-html, es una biblioteca de plantillas de HTML eficiente, expresiva y extensible para JavaScript.
Si se usa directamente en los archivos HTML, el modelo de programación mental está orientado directamente al resultado.
Como programador, escribes una plantilla de cómo se verá el resultado final, y lit-html llena los vacíos de forma dinámica según tus datos y conecta los objetos de escucha de eventos.
La app usa elementos personalizados de terceros, como <sl-progress-ring>
de Shoelace o un elemento personalizado implementado por el usuario llamado <human-duration>
.
Dado que los elementos personalizados tienen una API declarativa (por ejemplo, el atributo percentage
del anillo de progreso), trabajan bien con lit-html, como puedes ver en la siguiente lista.
<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>
Modelo de programación
Cada página tiene una clase Page
correspondiente que llena de vida el marcado lit-html, ya que proporciona implementaciones de los controladores de eventos y los datos de cada página.
Esta clase también admite métodos de ciclo de vida, como onShow()
, onHide()
, onLoad()
y onUnload()
.
Las páginas tienen acceso a un almacén de datos que sirve para compartir el estado global y el estado por página persistente opcionalmente.
Todas las cadenas se administran de manera central, por lo que la internacionalización está integrada.
El navegador controla el enrutamiento, básicamente, de forma gratuita, ya que todo lo que hace la app es activar o desactivar la visibilidad del iframe y, en el caso de las páginas creadas de forma dinámica, cambiar el atributo src
del iframe del marcador de posición.
En el siguiente ejemplo, se muestra el código para cerrar una página creada de forma dinámica.
import Page from '../page.js';
const page = new Page({
eventHandlers: {
back: (e) => {
e.preventDefault();
window.top.history.back();
},
},
});
Diseño
El diseño de las páginas se realiza por página en su propio archivo CSS con alcance.
Esto significa que, por lo general, los elementos se pueden abordar directamente por sus nombres, ya que no se pueden producir conflictos con otras páginas.
Los estilos globales se agregan a cada página, por lo que no es necesario declarar los parámetros de configuración centrales, como font-family
o box-sizing
, de forma reiterada.
Aquí también se definen los temas y las opciones de modo oscuro.
En la siguiente lista, se muestran las reglas de la página Preferences que establece los diversos elementos del formulario en una cuadrícula.
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;
}
Bloqueo de activación de pantalla
Durante un entrenamiento, la pantalla no debe apagarse. En los navegadores que lo admiten, HIIT Time lo realiza a través de un bloqueo de activación de la pantalla. En el siguiente fragmento, se muestra cómo hacerlo.
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();
}
});
}
Prueba la aplicación
La aplicación HIIT Time está disponible en GitHub. Puedes jugar con la demostración en una ventana nueva o directamente en el iframe incorporado a continuación, que simula un dispositivo móvil.
Agradecimientos
Este artículo fue revisado por Joe Medley, Kayce Basques, Milica Mihajlija, Alan Kent y Keith Gu.