Sprache

Kontakt aufnehmen

Android-App · seit 2025

ClearLap

Android-Stoppuhr mit Rundenzeiten, gebaut als eine Offline-Webseite in einer Cordova-Hülle.

Veröffentlicht
Laufzeitberechtigungen
Keine
Werbung
Keine
Preis
Kostenlos
  • HTML
  • CSS
  • JavaScript
  • Apache Cordova (Android)
  • Web Audio API
  • Screen Wake Lock API
  • Vibration API
Google Play play.google.com

Über mich

ClearLap ist eine Stoppuhr mit Runden, Hervorhebung der besten und schlechtesten Runde, Klicktönen, Vibration, wo die WebView sie unterstützt, und einem dunklen Vollbild-Layout. Sie hat keine Werbung, keine Konten, keine Netzwerknutzung und keine Plugins: Die ganze App ist eine einzige HTML-Datei von etwa 8 KB mit Inline-CSS und -JavaScript.

Die Ingenieursarbeit steckt darin, die Grundlagen innerhalb einer WebView korrekt umzusetzen: eine Zeit, die nicht driftet, wenn Frames ausfallen oder die App im Hintergrund läuft, Runden, die sich immer zur Gesamtzeit addieren, ein Screen Wake Lock, der angefordert wird, wo die WebView ihn unterstützt, und Feedback-Töne, die ohne Audiodateien synthetisiert werden.

ClearLap · Stoppuhr

was gelöst wurde

LIVEseit 2025

Zeitmessungskern

im Produkt

Was es bringtDie Zeit bleibt exakt, wenn das Telefon die App versteckt, eine Pause fügt nie Zeit hinzu und nimmt keine weg, Runden addieren sich zur Gesamtzeit, und die Ziffern springen nicht hin und her.

Problem
Eine Stoppuhr in einer WebView kann sich nicht darauf verlassen, dass Timer oder Frame-Callbacks die Zeit zählen: Sie werden gedrosselt, übersprungen oder pausiert, wenn die App im Hintergrund ist.
Lösung

Die Zeit wird immer aus Zeitstempeln berechnet; die Frame-Schleife zeichnet nur. Pause, Fortsetzen und Runden sind Arithmetik auf gespeicherten Zeitstempeln.

Stack
  • JavaScript
  • requestAnimationFrame
Aufgaben · 5
Intervallzähler driften und bleiben stehen, solange die App verborgen istDie verstrichene Zeit ist Date.now() minus ein Startzeitstempel und wird bei jedem Frame neu berechnet

tick() erhöht nie einen Zähler. Es setzt elapsed = Date.now() - startTs und zeichnet neu. Fallen Frames aus oder setzt die WebView requestAnimationFrame im Hintergrund aus, zeigt der nächste Frame die richtige Zeit, weil nichts akkumuliert wurde.

Pause und Fortsetzen dürfen keine Zeit verlieren oder hinzufügenBeim Fortsetzen wird der Start neu gesetzt: startTs = Date.now() - elapsed

Pause bricht den Animations-Frame ab und lässt elapsed unverändert. Fortsetzen verschiebt startTs um den bereits verstrichenen Betrag zurück, sodass die Formel in tick() beim selben Wert weitermacht, ohne eine separate Variable für die „Pausendauer“.

Zurücksetzen bricht die Frame-Schleife ab, setzt den Zustand auf null und deaktiviert die Schaltfläche „Lap“; doLap() kehrt außerdem vorzeitig zurück, wenn die Uhr nicht läuft.

Rundendauern müssen sich exakt zur Gesamtzeit addierenJede Runde ist elapsed minus die vorherige Rundenmarke, sodass die Runden teleskopartig die Gesamtzeit ergeben

doLap() speichert die Rundennummer, ihre Dauer (elapsed - lapElapsed) und die laufende Gesamtzeit und setzt dann lapElapsed auf elapsed. Weil jede Runde dort beginnt, wo die vorherige endete, entspricht die Summe der Rundendauern per Konstruktion der Gesamtzeit, ohne Rundungslücke.

Der Zähler der aktuellen Runde unter der Hauptzeit verwendet dieselbe Marke: elapsed - lapElapsed.

Die Markierung der besten und schlechtesten Runde ist bei einer einzigen Runde sinnlosBei jedem Rendern Minimum und Maximum über alle Runden berechnen; nur hervorheben, wenn es 2+ gibt

renderLaps() baut die Liste mit der neuesten Runde zuerst neu auf, ermittelt die kürzeste und längste Dauer und setzt die Klassen best/worst nur, wenn mehr als eine Runde existiert.

Die Ziffern springen seitwärts, wenn sich die Zeit ändertMonospace-Schrift plus font-variant-numeric: tabular-nums bei jeder Zeitanzeige

Alle Zeitfelder verwenden Ziffern fester Breite, sodass die Anzeige bei 16vw nicht verrutscht, wenn sich die Hundertstelsekunden ändern. fmt() gibt immer mm:ss.cc mit führenden Nullen aus.

Geräteverhalten ohne Plugins

im Produkt

Was es bringtDer Bildschirm bleibt bei langen Sitzungen an, die App füllt den ganzen Bildschirm, und Tippen gibt einen Piepton und ein Summen, ohne je die Zeitmessung zu stören.

Problem
Bildschirm an, Vollbild, Ton und Haptik kommen üblicherweise aus Cordova-Plugins, und Browser erlauben manche davon erst nach einer Nutzergeste.
Lösung

Web-Plattform-APIs der WebView nutzen, wo sie existieren, sie im richtigen Moment anfordern und jede davon optional machen, damit eine fehlende API die Zeitmessung nie stört.

Stack
  • Screen Wake Lock API
  • Fullscreen API
  • Web Audio API
  • Vibration API
  • Cordova config.xml
Aufgaben · 4
Der Bildschirm schaltet sich während einer langen Zeitmessung ausEinen Screen Wake Lock anfordern, wo die WebView ihn unterstützt, und erneut, sobald die Seite wieder sichtbar wird

Das System gibt einen Wake Lock frei, wenn die Seite verborgen wird. requestWakeLock() läuft beim Laden der Seite und bei jedem visibilitychange zu visible, und der Release-Listener löscht die Referenz. Der Aufruf wird übersprungen, wenn navigator.wakeLock fehlt, und Fehler werden verschluckt, sodass die Stoppuhr in beiden Fällen gleich funktioniert.

Vollbild lässt sich nur aus einer Nutzergeste heraus anfordernBeim ersten Tippen anfordern, mit webkit-Fallback, dann den Listener entfernen

Ein einmaliger touchstart-Listener ruft requestFullscreen oder webkitRequestFullscreen auf und entfernt sich selbst. config.xml setzt außerdem die Cordova-Einstellung Fullscreen für den Android-Build.

Klicktöne ohne Audiodateien und ohne Knacken oder KnallenKurze Sinus-Pieptöne mit Web Audio synthetisieren, mit exponentiellem Gain-Fade-out

beep() erzeugt bei jedem Druck einen Oszillator und einen Gain-Knoten und fährt den Gain über die Tondauer exponentiell auf 0.001 herunter, was das Knacken eines abrupten Stopps vermeidet. Start, Runde und Zurücksetzen verwenden unterschiedliche Tonhöhen und Längen (880, 1100 und 440 Hz).

Der AudioContext wird verzögert im ersten Button-Handler erzeugt, was die Autoplay-Regel erfüllt, dass Audio aus einer Nutzergeste heraus starten muss.

Fehler im Feedback dürfen die Zeitmessung nie unterbrechenTon und Vibration laufen vor den Zustandsänderungen, nur wo die API existiert, und innerhalb von try/catch

beep() und vibe() fangen jede Exception ab, und vibe() ruft navigator.vibrate nur auf, wo es verfügbar ist. Start, Runde und Zurücksetzen fordern unterschiedliche Vibrationslängen an (25, 15 und 40 ms).

config.xml deklariert keine Plugins und die Seite macht keine Netzwerkaufrufe, sodass der Zeitmessungskern nur von der WebView abhängt.

Zahlen

aus dem Produkt
  • Läuft auf Android 5.0 und neuer
  • App-Code etwa 8 KB Eine HTML-Datei, keine Frameworks.
  • Genauigkeit 1/100 s