Idioma

Contactar

App de Android · desde 2025

ClearLap

Cronómetro de Android con tiempos de vuelta, creado como una única página web sin conexión dentro de un contenedor Cordova.

Publicado
Permisos en tiempo de ejecución
Ninguno
Anuncios
Ninguno
Precio
Gratis
  • HTML
  • CSS
  • JavaScript
  • Apache Cordova (Android)
  • Web Audio API
  • Screen Wake Lock API
  • Vibration API
Google Play play.google.com

Sobre mí

ClearLap es un cronómetro con vueltas, resaltado de la mejor y la peor vuelta, sonidos de pulsación, vibración donde el WebView la admite y un diseño oscuro a pantalla completa. No tiene anuncios, ni cuentas, ni uso de red, ni plugins: toda la app es un único archivo HTML de unos 8 KB con CSS y JavaScript integrados.

El trabajo de ingeniería está en hacer bien lo básico dentro de un WebView: un tiempo que no se desvía cuando se pierden fotogramas o la app está en segundo plano, vueltas que siempre suman el total, un bloqueo de suspensión de pantalla solicitado donde el WebView lo admite y sonidos de respuesta sintetizados sin archivos de audio.

ClearLap · cronómetro

qué se resolvió

ACTIVOdesde 2025

Núcleo de medición del tiempo

en el producto

Qué aportaEl tiempo se mantiene exacto cuando el teléfono oculta la app, pausar nunca suma ni pierde tiempo, las vueltas suman el total y los dígitos no saltan de un lado a otro.

Problema
Un cronómetro en un WebView no puede fiarse de los temporizadores ni de las llamadas por fotograma para contar el tiempo: se ralentizan, se omiten o se pausan cuando la app está en segundo plano.
Solución

El tiempo siempre se calcula a partir de marcas de tiempo; el bucle de fotogramas solo dibuja. Pausar, reanudar y las vueltas son aritmética sobre marcas de tiempo almacenadas.

Stack
  • JavaScript
  • requestAnimationFrame
Tareas · 5
Los contadores por intervalo se desvían y se detienen mientras la app está ocultaEl tiempo transcurrido es Date.now() menos una marca de tiempo de inicio, recalculado en cada fotograma

tick() nunca incrementa un contador. Establece elapsed = Date.now() - startTs y redibuja. Si se pierden fotogramas o el WebView suspende requestAnimationFrame en segundo plano, el siguiente fotograma muestra el tiempo correcto porque no se acumuló nada.

Pausar y reanudar no debe perder ni añadir tiempoAl reanudar, se reajusta el inicio: startTs = Date.now() - elapsed

Pausar cancela el fotograma de animación y deja elapsed tal cual. Reanudar retrocede startTs en la cantidad ya transcurrida, de modo que la fórmula de tick() continúa desde el mismo valor sin necesidad de una variable aparte de 'duración en pausa'.

Reiniciar cancela el bucle de fotogramas, pone el estado a cero y desactiva el botón de vuelta; doLap() también termina antes de tiempo cuando el cronómetro no está en marcha.

Las duraciones de las vueltas deben sumar exactamente el tiempo totalCada vuelta es el tiempo transcurrido menos la marca de la vuelta anterior, de modo que las vueltas se encadenan hasta el total

doLap() almacena el número de vuelta, su duración (elapsed - lapElapsed) y el total acumulado, y después mueve lapElapsed a elapsed. Como cada vuelta empieza donde terminó la anterior, la suma de las duraciones de las vueltas es igual al total por construcción, sin diferencias de redondeo.

El contador de la vuelta actual bajo el tiempo principal usa la misma marca: elapsed - lapElapsed.

Marcar la mejor y la peor vuelta no tiene sentido con una sola vueltaCalcular el mínimo y el máximo de todas las vueltas en cada renderizado; resaltar solo cuando hay 2 o más

renderLaps() reconstruye la lista con la más reciente primero, encuentra las duraciones más corta y más larga y añade las clases best/worst solo cuando existe más de una vuelta.

Los dígitos se desplazan lateralmente al cambiar el tiempoFuente monoespaciada más font-variant-numeric: tabular-nums en cada indicador de tiempo

Todos los campos de tiempo usan dígitos de ancho fijo, así que la pantalla a 16vw no se desplaza al cambiar las centésimas. fmt() siempre imprime mm:ss.cc con relleno de ceros.

Comportamiento del dispositivo sin plugins

en el producto

Qué aportaLa pantalla se mantiene encendida durante sesiones largas, la app ocupa toda la pantalla y los toques dan un pitido y una vibración, sin perturbar nunca la medición del tiempo.

Problema
Mantener la pantalla encendida, la pantalla completa, el sonido y la respuesta háptica suelen venir de plugins de Cordova, y los navegadores permiten algunos de ellos solo tras un gesto del usuario.
Solución

Usar las API de la plataforma web del WebView donde existan, solicitarlas en el momento adecuado y hacer que cada una sea opcional, de modo que una API ausente nunca rompa la medición del tiempo.

Stack
  • Screen Wake Lock API
  • Fullscreen API
  • Web Audio API
  • Vibration API
  • Cordova config.xml
Tareas · 4
La pantalla se apaga durante una sesión larga de mediciónSolicitar un bloqueo de suspensión de pantalla donde el WebView lo admita, y de nuevo cada vez que la página vuelva a ser visible

El sistema libera el bloqueo de suspensión cuando la página se oculta. requestWakeLock() se ejecuta al cargar la página y en cada visibilitychange a visible, y el listener de liberación borra la referencia. La llamada se omite cuando falta navigator.wakeLock y los errores se descartan, así que el cronómetro funciona igual en ambos casos.

La pantalla completa solo se puede solicitar desde un gesto del usuarioSolicitarla en el primer toque, con alternativa webkit, y después eliminar el listener

Un listener touchstart de un solo uso llama a requestFullscreen o webkitRequestFullscreen y se elimina a sí mismo. config.xml también establece la preferencia Fullscreen de Cordova para la compilación de Android.

Sonidos de pulsación sin archivos de audio y sin clics ni chasquidosSintetizar pitidos sinusoidales cortos con Web Audio y un fundido de salida exponencial de la ganancia

beep() crea un oscilador y un nodo de ganancia por pulsación y lleva la ganancia exponencialmente a 0.001 durante la duración del pitido, lo que evita el chasquido de una parada brusca. Inicio, vuelta y reinicio usan tonos y duraciones distintos (880, 1100 y 440 Hz).

El AudioContext se crea de forma diferida dentro del primer manejador de botón, lo que cumple la regla de reproducción automática de que el audio debe empezar desde un gesto del usuario.

Los fallos de la respuesta al usuario nunca deben interrumpir la medición del tiempoEl sonido y la vibración se ejecutan antes de los cambios de estado, solo donde existe la API y dentro de try/catch

beep() y vibe() capturan todas las excepciones, y vibe() llama a navigator.vibrate solo donde está disponible. Inicio, vuelta y reinicio solicitan vibraciones de distinta duración (25, 15 y 40 ms).

config.xml no declara plugins y la página no hace llamadas de red, así que el núcleo de medición del tiempo depende únicamente del WebView.

Cifras

del producto
  • Funciona en Android 5.0 y posteriores
  • Código de la app unos 8 KB Un archivo HTML, sin frameworks.
  • Precisión 1/100 s