O domínio do app
Para mostrar a maneira de programar miniapps aplicada a um app da Web, precisei de uma ideia de app pequena, mas completa. O treino intervalado de alta intensidade (HIIT, na sigla em inglês) é uma estratégia de exercícios cardiovasculares que alterna séries de períodos curtos de exercícios anaeróbicos intensos com períodos de recuperação menos intensos. Muitos treinos HIIT usam cronômetros, por exemplo, esta sessão on-line de 30 minutos do canal do YouTube The Body Coach TV.
App de exemplo do HIIT Time
Para este capítulo, criei um exemplo básico de um aplicativo de cronômetro de HIIT chamado "HIIT Time", que permite ao usuário definir e gerenciar vários cronômetros, sempre com um intervalo de alta e baixa intensidade, e, em seguida, selecionar um deles para uma sessão de treinamento. É um app responsivo com uma barra de navegação, uma barra de guias e três páginas:
- Treino:é a página ativa durante um treino. Ele permite que o usuário selecione um dos cronômetros e tem três anéis de progresso: o número de séries, o período ativo e o período de descanso.
- Timers: gerencia timers existentes e permite que o usuário crie novos.
- Preferências: permite alternar efeitos sonoros e saída de fala e selecionar idioma e tema.
As capturas de tela a seguir mostram uma impressão do aplicativo.
Estrutura do app
Conforme descrito acima, o aplicativo consiste em uma barra de navegação, uma barra de guias e três páginas, organizadas em uma grade.
A barra de navegação e a barra de guias são implementadas como iframes com um contêiner <div>
entre eles e mais três iframes
para as páginas, sendo que um deles está sempre visível e depende da seleção ativa na barra de guias.
Um iframe final que aponta para about:blank
serve para páginas criadas dinamicamente no app, que são necessárias para modificar
timers existentes ou criar novos.
Chamo esse padrão de app de página única com várias páginas (MPSPA).
Marcação lit-html baseada em componentes
A estrutura de cada página é realizada como um esqueleto lit-html
que é avaliado dinamicamente no momento da execução.
Para saber mais sobre o lit-html, ele é uma biblioteca de modelos HTML eficiente, expressiva e extensível para JavaScript.
Ao usar o modelo diretamente nos arquivos HTML, a programação mental é orientada para a saída.
Como programador, você escreve um modelo de como será a saída final.
O lit-html preenche as lacunas de forma dinâmica com base nos seus dados e conecta os listeners de eventos.
O app usa elementos personalizados de terceiros, como o <sl-progress-ring>
do Shoelace ou um elemento personalizado autoimplementado chamado <human-duration>
.
Como os elementos personalizados têm uma API declarativa (por exemplo, o atributo percentage
do anel de progresso),
eles funcionam bem com o lit-html, como você pode conferir na lista abaixo.
<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 programação
Cada página tem uma classe Page
correspondente que preenche a marcação lit-html com implementações
dos manipuladores de eventos e fornece os dados para cada página.
Essa classe também oferece suporte a métodos de ciclo de vida, como onShow()
, onHide()
, onLoad()
e onUnload()
.
As páginas têm acesso a um repositório de dados que serve para compartilhar o estado por página e o estado global com opção de persistência.
Todas as strings são gerenciadas centralmente, portanto, a internacionalização está integrada.
O roteamento é processado pelo navegador, basicamente sem custo, já que tudo o que o app faz é alternar a visibilidade do iframe e,
para páginas criadas dinamicamente, mudar o atributo src
do iframe de marcador de posição.
O exemplo abaixo mostra o código para fechar uma página criada dinamicamente.
import Page from '../page.js';
const page = new Page({
eventHandlers: {
back: (e) => {
e.preventDefault();
window.top.history.back();
},
},
});
Estilo
O estilo das páginas acontece em cada página no próprio arquivo CSS de escopo.
Isso significa que os elementos geralmente podem ser endereçados diretamente pelos nomes deles,
já que não podem ocorrer conflitos com outras páginas.
Os estilos globais são adicionados a cada página. Portanto, as configurações centrais, como font-family
ou box-sizing
,
não precisam ser declaradas repetidamente.
É aqui que as opções de temas e modo escuro são definidas.
A lista abaixo mostra as regras da página "Preferências", que define os vários elementos do formulário
em uma grade.
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;
}
Bloqueio de tela ativada
A tela não será desligada durante um treino. Em navegadores compatíveis, o HIIT Time detecta isso com um wake lock de tela. O snippet abaixo mostra como fazer isso.
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();
}
});
}
Teste o aplicativo
O aplicativo HIIT Time está disponível no GitHub. Você pode jogar com a demonstração em uma nova janela ou diretamente na incorporação de iframe abaixo, que simula um dispositivo móvel.
Agradecimentos
Este artigo foi revisado por Joe Medley, Kayce Basques, Milica Mihajlija, Alan Kent e Keith Gu.