ClearLap
Cronómetro Android com tempos de volta, construído como uma única página web offline dentro de um invólucro Cordova.
- Publicado
- Permissões em tempo de execução
- Nenhuma
- Anúncios
- Nenhuma
- Preço
- Grátis
Sobre
O ClearLap é um cronómetro com voltas, destaque da melhor e da pior volta, sons de clique, vibração onde o WebView a suporta e um ecrã completo escuro. Não tem anúncios, contas, utilização de rede nem plugins: a aplicação inteira é um único ficheiro HTML de cerca de 8 KB, com CSS e JavaScript embebidos.
O trabalho de engenharia está em fazer o essencial corretamente dentro de um WebView: um tempo que não se desvia quando se perdem frames ou a aplicação está em segundo plano, voltas que somam sempre o total, um bloqueio para manter o ecrã ligado pedido onde o WebView o suporta, e sons de feedback sintetizados sem ficheiros de áudio.
ClearLap · cronómetro
o que foi resolvido
Núcleo de temporização
no produtoO que ofereceO tempo mantém-se exato quando o telemóvel esconde a aplicação, a pausa nunca acrescenta nem perde tempo, as voltas somam o total e os dígitos não saltam de um lado para o outro.
- Problema
- Um cronómetro num WebView não pode confiar em temporizadores ou callbacks de frames para contar o tempo: são limitados, ignorados ou suspensos quando a aplicação está em segundo plano.
- Solução
O tempo é sempre calculado a partir de timestamps; o ciclo de frames apenas desenha. Pausa, retoma e voltas são aritmética sobre timestamps guardados.
- Stack
Os contadores por intervalo desviam-se e param enquanto a aplicação está escondidaO tempo decorrido é Date.now() menos um timestamp de início, recalculado a cada frame
tick() nunca incrementa um contador. Define elapsed = Date.now() - startTs e volta a desenhar. Se se perderem frames ou o WebView suspender requestAnimationFrame em segundo plano, o frame seguinte mostra o tempo correto porque nada foi acumulado.
Pausar e retomar não pode perder nem acrescentar tempoAo retomar, reajusta-se o início: startTs = Date.now() - elapsed
A pausa cancela o frame de animação e mantém elapsed tal como está. A retoma recua startTs na quantidade já decorrida, pelo que a fórmula em tick() continua a partir do mesmo valor, sem uma variável separada de 'duração em pausa'.
O reset cancela o ciclo de frames, repõe o estado a zero e desativa o botão Lap; doLap() também termina de imediato quando o cronómetro não está a correr.
A duração das voltas tem de somar exatamente o tempo totalCada volta é o tempo decorrido menos a marca da volta anterior, pelo que as voltas se encadeiam até ao total
doLap() guarda o número da volta, a sua duração (elapsed - lapElapsed) e o total acumulado, e depois move lapElapsed para elapsed. Como cada volta começa onde a anterior terminou, a soma das durações das voltas é igual ao total por construção, sem diferença de arredondamento.
O contador da volta atual, por baixo do tempo principal, usa a mesma marca: elapsed - lapElapsed.
Marcar a melhor e a pior volta não faz sentido com uma só voltaCalcular o mínimo e o máximo de todas as voltas em cada renderização; destacar apenas quando há 2 ou mais
renderLaps() reconstrói a lista com a mais recente primeiro, encontra as durações mais curta e mais longa e acrescenta as classes best/worst apenas quando existe mais de uma volta.
Os dígitos saltam para os lados quando o tempo mudaTipo de letra monoespaçado mais font-variant-numeric: tabular-nums em todos os mostradores de tempo
Todos os campos de tempo usam dígitos de largura fixa, pelo que o mostrador a 16vw não se desloca quando os centésimos de segundo mudam. fmt() imprime sempre mm:ss.cc com preenchimento de zeros.
Comportamento do dispositivo sem plugins
no produtoO que ofereceO ecrã mantém-se ligado em sessões longas, a aplicação ocupa o ecrã inteiro e os toques dão um bip e uma vibração, sem nunca perturbar a temporização.
- Problema
- Ecrã ligado, ecrã completo, som e háptica costumam vir de plugins Cordova, e os navegadores permitem alguns deles apenas após um gesto do utilizador.
- Solução
Usar as APIs da plataforma web do WebView onde existem, pedi-las no momento certo e tornar cada uma opcional, para que uma API em falta nunca estrague a temporização.
- Stack
O ecrã apaga-se durante uma sessão longa de cronometragemPedir um screen wake lock onde o WebView o suporta, e de novo sempre que a página se torna visível
O sistema liberta um wake lock quando a página fica escondida. requestWakeLock() corre quando a página carrega e em cada visibilitychange para visível, e o listener de libertação limpa a referência. A chamada é ignorada quando navigator.wakeLock não existe e os erros são engolidos, pelo que o cronómetro funciona da mesma forma nos dois casos.
O ecrã completo só pode ser pedido a partir de um gesto do utilizadorPedi-lo no primeiro toque, com alternativa webkit, e depois remover o listener
Um listener touchstart de utilização única chama requestFullscreen ou webkitRequestFullscreen e remove-se a si próprio. O config.xml define também a preferência Fullscreen do Cordova para a versão Android.
Sons de clique sem ficheiros de áudio e sem estalidosSintetizar bips curtos de onda sinusoidal com Web Audio e um fade-out exponencial do ganho
beep() cria um oscilador e um nó de ganho por toque e leva o ganho exponencialmente a 0.001 ao longo da duração do bip, o que evita o estalido de uma paragem abrupta. Início, volta e reset usam tons e durações diferentes (880, 1100 e 440 Hz).
O AudioContext é criado de forma lazy dentro do primeiro handler de botão, o que cumpre a regra de autoplay segundo a qual o áudio tem de começar a partir de um gesto do utilizador.
As falhas de feedback nunca podem interromper a temporizaçãoSom e vibração correm antes das mudanças de estado, só onde a API existe e dentro de try/catch
beep() e vibe() apanham todas as exceções, e vibe() chama navigator.vibrate apenas onde está disponível. Início, volta e reset pedem durações de vibração diferentes (25, 15 e 40 ms).
O config.xml não declara plugins e a página não faz chamadas de rede, pelo que o núcleo de temporização depende apenas do WebView.
Números
do produto- Funciona em Android 5.0 e posterior
- Código da aplicação cerca de 8 KB Um ficheiro HTML, sem frameworks.
- Precisão 1/100 s