Idioma

Entrar em contacto

Aplicação Android · desde 2025

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
  • HTML
  • CSS
  • JavaScript
  • Apache Cordova (Android)
  • Web Audio API
  • Screen Wake Lock API
  • Vibration API
Google Play play.google.com

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

ATIVOdesde 2025

Núcleo de temporização

no produto

O 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
  • JavaScript
  • requestAnimationFrame
Tarefas · 5
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 produto

O 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
  • Screen Wake Lock API
  • Fullscreen API
  • Web Audio API
  • Vibration API
  • Cordova config.xml
Tarefas · 4
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