JavaScript/Tutorials/Timer und Countdown

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Hast du jemals einen Countdown-Timer für ein Projekt benötigt? Eine Suche im Netz findet viele Bibliotheken - eine Lösung mit Vanilla-JS ist aber gar nicht so schwierig.

In diesem Tutorial lernst Du, wie man einen Timer baut, der die Zeit hoch- oder bei einem Countdown herunterzählt. Dabei verwenden wir neben JavaScript HTML, SVG und gestalten alles mit CSS. Deshalb solltest Du bereits Grundkenntnisse in diesen Technologien vorweisen können.

Vorüberlegungen

JavaScript-Programme sind nicht mit anderen Computerprogrammen zu vergleichen, die eine bestimmten Startzeit und Endzeit ihrer Ausführung haben. JavaScripte werden immer wieder für kurze Zeit aktiv, etwa wenn auf Ereignisse reagiert wird. Wenn ein JavaScript tatsächlich mehrere Sekunden an einem Stück läuft und die gesamten Rechenkapazitäten in Beschlag nimmt, friert der Browser ein oder zeigt einen Dialog, mit dem das Script abgebrochen werden kann.

Dementsprechend gibt es in JavaScript keinen Befehl sleep, der einfach für eine gegebene Zeit nichts tut. So etwas ist in JavaScript nicht möglich. Allerdings lassen sich über window.setTimeout und window.setInterval Funktionen nach einer angegebenen Wartezeit aufrufen.

Soll ein Script über längere Zeit laufen und zwischen einzelnen Befehlen eine Wartezeit verstreichen lassen, ist dies in JavaScript nur mit window.setTimeout über das »Stop & Go«-Verfahren möglich. Auf diese Weise können z.B. Animationen umgesetzt werden.

Ein verzögerter Aufruf sieht in manch anderen Sprachen schematisch so aus:

Beispiel
befehl1;
sleep(5);
befehl2;

In JavaScript hingegen notieren wir eine Funktion, die wir zeitverzögert aufrufen:

Beispiel
befehl1;
function befehl2 () {}
window.setTimeout(befehl2, 5000);

Knifflig wird es nun, wenn ein Befehl mit sich ändernden Parametern mehrmals ausgeführt werden soll und dazwischen jeweils eine Zeitspanne liegen soll. Ein Countdown etwa ließe sich in anderen Sprachen schematisch so realisieren:

Beispiel
for ($i = 10; $i > 0; i--) {
  echo $i . "\n";
  sleep(1);
}

In JavaScript wird dies üblicherweise mit einer Funktion gelöst, die sich immer wieder selbst verzögert aufruft und dabei den Schleifenzähler übergibt:

Beispiel
function countdown (i) {
  if (i == undefined) {
    // Startwert
    i = 10;
  }
  alert(i);
  if (i > 0) {
    i--;
    // Funktion verzögert aufrufen
    var timeout = window.setTimeout('countdown(' + i + ')', 1000);
  }
}
// Countdown anstoßen
countdown();

Wir sehen hier bereits, wie kompliziert die Umsetzung eines einfachen Countdowns wird. Diese verbreitete Umsetzung lässt sich zunächst durch den Einsatz von Closures vereinfachen. Damit lässt sich die Übergabe von Parametern einfacher gestalten: Anstatt window.setTimeout("countdown(" + i + ")", 1000); können wir ebenso eine anonyme Funktion notieren, die als Closure wirkt und in der die lokale Variable i zur Verfügung steht: window.setTimeout(function () { countdown(i) }, 1000);. Dies ist vor allem sinnvoll, wenn mehr und komplexere Variablen übergeben werden müssen, die nicht so einfach in einen String linearisiert werden können.

Wenn mehrere Funktionen periodisch aufgerufen werden sollen, so ist es umständlich, denselben Mechanismus immer wieder neu zu notieren. Im Folgenden wird eine Helferfunktion vorgestellt, mit der sich solche Aufgaben komfortabel umsetzen lassen.


Vorstellung der Timer-Methode

Timer mit Date.now()

Die Methode setInterval ist eigentlich dafür prädestiniert, eine Zeit entweder herauf-, oder bei einem Countdown herunterzuzählen. Da JavaScript single-threaded ist, kann es aber bei der Ausführung von Skripten zu kleineren Verzögerungen kommen, sodass solche Timer nie sekundengenau funktionieren.

Mit date.now() kannst du einen solchen Timer problemlos takten:

Jetzt geht's los! ansehen …
  function timer() {
    const startTime = Date.now();

    let interval = setInterval(function() {
       let elapsedTime = Date.now() - startTime;
       document.getElementById('timer').textContent = (elapsedTime / 1000).toFixed(1);
    }, 100);
  }

In dieser timer-Funktion wird anfangs ein Startzeitpunkt mit Date.now() festgelegt und der Variablen startTime zugewiesen.

Mit setInterval wird nun in einer anynomen Funktion der aktuelle Zeitpunkt ermittelt und um den Startzeitpunkt vermindert.

Diese elapsedTime wird bei der Ausgabe entsprechend formatiert:

  1. mit einer Division durch 1000 erhält man die Sekunden,
  2. mit toFixed wird die Anzahl Nachkommastellen passend abgeschnitten, sodass nur Zehntelsekunden sichtbar sind.

Countdown - rückwärts zählen

Natürlich kann man mit JavaScript auch rückwärts zählen:

const count = 15;
const timer = setInterval(function() {
  count--;
  console.log(count);
  if (count === 0) {
    clearInterval(timer);
    console.log("Countdown finished!");
  }
}, 1000);

In seiner einfachsten Variante verzichten wir auf das Date-Objekt und überlassen das Zählen der setInterval()-Methode, deren zweiter Parameter die Angabe 1000 (Millisekunden) erhält und die so jede Sekunde die Zählvariable count einmal mit -- um eins verringert und dann ausgibt.

Wenn count 0 beträgt, wird mit clearInterval das Intervall gelöscht und eine Meldung ausgegeben.

Als Modifikation unseres ersten Beispiels

Der letzte Countdown! ansehen …
const duration = 15; 
  
function timer(duration) {
	const startTime = Date.now();
	const targetTime = startTime + (duration * 1000); // Convert seconds to milliseconds

	let interval = setInterval(function() {
		let elapsedTime = targetTime - Date.now();
		if (elapsedTime > 0) {
			document.getElementById('timer').textContent = (elapsedTime / 1000).toFixed(1);
		} else {
			clearInterval(interval);
			document.getElementById('timer').textContent = "0.0";
			console.log("Countdown finished!");            
		}
	}, 100);
}

Visualisierung mit CSS-Animation

Analog oder digital?

Mit SVG kannst du auch das Zifferblatt einer Uhr und Zeiger darstellen:

Ausführungszeiten messen

Wenn du Lade- und Laufzeiten von Skripten messen und vergleichen willst, reicht das Date-Objekt von JavaScript nicht aus, da die Systemzeit des Anwenderrechners nicht hinreichend genau ist. Theoretisch misst sie in Millisekunden – praktisch verzögert sich die Ausführung von Skripten durch die single-threaded Natur von JavaScript. Darüber hinaus wird die Systemzeit alle 15-20 Minuten synchronisiert, sodass es hier zu Sprüngen von mehreren Millisekunden kommen kann.

früher: Messen mit dem Date-Objekt
var start = new Date();
    // CODE, der gemessen werden soll
var time = new Date() - start;
// time is the number of milliseconds it takes to execute the script

High Resolution Time

Der DOMHighResTimeStamp kann zum Speichern von Zeitwerten verwendet werden. Im Unterschied zum Date-Objekt kann es dabei bis zu 5/1000 einer Millisekunde (5 µs (Microsekunden)) unterscheiden.[2]

performance.now()

Die Methode window.performance.now ermöglicht ähnlich wie Date.now() das Messen und Vergleichen von Zeitpunkten. Dabei bietet es aber timestamps mit einer kleineren Auflösung als die im Date-Objekt verwendeten Millisekunden, um die Seitenperformance messen zu können.[3]

Grundlage ist nicht der 01.01.1970, sondern die in performance.timing.navigationStart gespeicherte Zeit, in der die fragliche Seite geladen wurde.

heute: Messen mit performance.now()
var start = performance.now();
    // CODE, der gemessen werden soll
var time = performance.now();
console.log('Dauer: ' + (time - start) + ' ms.');


Siehe auch

  • Einführung in Zeit & Datum
    Date-Icon.svg
  • Monatskalender
    Date-Icon.svg
  • input type="date" und mehr
    HTML5 bietet eine Vielzahl von neuen Eingabe-Typen für Datums- und Zeitangaben.


Weblinks

  1. Dies ist eine umfassende Neubearbeitung des SELFHTML-aktuell-Artikels Komfortable Timer-Funktion - ein Helferscript zum wiederholten Ausführen von Funktionen.
    Das Script wurde im Zusammenhang mit einer Forumsdiskussion von Struppi entwickelt und von Mathias Schäfer dokumentiert.
    2009 wurde mit ES5 Date.now() eingeführt, das unser Timer-Script radikal vereinfacht.
  2. W3C: High Resolution Time
  3. MDN: Performance.now()