Haben Sie jemals einen Countdown-Timer für ein Projekt benötigt? Für so etwas ist es vielleicht natürlich, zu einem Plugin zu greifen, aber es ist tatsächlich viel einfacher, als Sie vielleicht denken, und erfordert nur das Trio aus HTML, CSS und JavaScript. Lassen Sie uns gemeinsam einen erstellen!
Darauf arbeiten wir hin
Hier sind einige Dinge, die der Timer tut und die wir in diesem Beitrag behandeln werden
- Zeigt die verbleibende Anfangszeit an
- Konvertiert den Zeitwert in das
MM:SSFormat - Berechnet die Differenz zwischen der verbleibenden Anfangszeit und der verstrichenen Zeit
- Ändert die Farbe, wenn die verbleibende Zeit gegen Null läuft
- Zeigt den Fortschritt der verbleibenden Zeit als animierten Ring an
OK, das wollen wir, also packen wir's an!
Schritt 1: Beginnen Sie mit dem grundlegenden Markup und den Stilen
Lassen Sie uns mit der Erstellung einer grundlegenden Vorlage für unseren Timer beginnen. Wir fügen eine svg mit einem circle-Element darin hinzu, um einen Timerring zu zeichnen, der die verstrichene Zeit anzeigt, und ein span-Element, um den Wert der verbleibenden Zeit anzuzeigen. Beachten Sie, dass wir den HTML-Code in JavaScript schreiben und ihn in das DOM injizieren, indem wir auf das Element #app abzielen. Sicher, wir könnten vieles davon in eine HTML-Datei verschieben, wenn das eher Ihr Ding ist.
document.getElementById("app").innerHTML = `
<div class="base-timer">
<svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<g class="base-timer__circle">
<circle class="base-timer__path-elapsed" cx="50" cy="50" r="45" />
</g>
</svg>
<span>
<!-- Remaining time label -->
</span>
</div>
`;
Jetzt, da wir etwas Markup zum Arbeiten haben, lassen Sie es uns ein wenig aufhübschen, damit wir eine gute visuelle Grundlage haben. Insbesondere werden wir
- die Größe des Timers festlegen
- den Füll- und Konturstrich des Kreis-Wrapper-Elements entfernen, damit wir die Form erhalten, aber die verstrichene Zeit durchscheinen lassen
- die Breite und Farbe des Rings festlegen
/* Sets the containers height and width */
.base-timer {
position: relative;
height: 300px;
width: 300px;
}
/* Removes SVG styling that would hide the time label */
.base-timer__circle {
fill: none;
stroke: none;
}
/* The SVG path that displays the timer's progress */
.base-timer__path-elapsed {
stroke-width: 7px;
stroke: grey;
}
Wenn das erledigt ist, erhalten wir eine grundlegende Vorlage, die so aussieht.

Schritt 2: Einrichten des Zeit-Labels
Wie Sie wahrscheinlich bemerkt haben, enthält die Vorlage ein leeres <span>, das die verbleibende Zeit enthalten wird. Wir werden diesen Platz mit einem richtigen Wert füllen. Wir haben zuvor gesagt, dass die Zeit im MM:SS Format vorliegen wird. Dazu erstellen wir eine Methode namens formatTimeLeft
function formatTimeLeft(time) {
// The largest round integer less than or equal to the result of time divided being by 60.
const minutes = Math.floor(time / 60);
// Seconds are the remainder of the time divided by 60 (modulus operator)
let seconds = time % 60;
// If the value of seconds is less than 10, then display seconds with a leading zero
if (seconds < 10) {
seconds = `0${seconds}`;
}
// The output in MM:SS format
return `${minutes}:${seconds}`;
}
Dann werden wir unsere Methode in der Vorlage verwenden
document.getElementById("app").innerHTML = `
<div class="base-timer">
<svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<g class="base-timer__circle">
<circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle>
</g>
</svg>
<span id="base-timer-label" class="base-timer__label">
${formatTime(timeLeft)}
</span>
</div>
`
Um den Wert innerhalb des Rings anzuzeigen, müssen wir unsere Stile etwas aktualisieren.
.base-timer__label {
position: absolute;
/* Size should match the parent container */
width: 300px;
height: 300px;
/* Keep the label aligned to the top */
top: 0;
/* Create a flexible box that centers content vertically and horizontally */
display: flex;
align-items: center;
justify-content: center;
/* Sort of an arbitrary number; adjust to your liking */
font-size: 48px;
}
OK, wir sind bereit, mit dem timeLeft-Wert zu spielen, aber der Wert existiert noch nicht. Lassen Sie ihn uns erstellen und den Anfangswert auf unser Zeitlimit setzen.
// Start with an initial value of 20 seconds
const TIME_LIMIT = 20;
// Initially, no time has passed, but this will count up
// and subtract from the TIME_LIMIT
let timePassed = 0;
let timeLeft = TIME_LIMIT;
Und wir sind einen Schritt näher.

Genau! Jetzt haben wir einen Timer, der bei 20 Sekunden beginnt... aber er zählt noch nicht. Bringen wir ihn zum Leben, damit er bis auf Null zählt.
Schritt 3: Herunterzählen
Denken wir darüber nach, was wir brauchen, um die Zeit herunterzuzählen. Im Moment haben wir einen timeLimit-Wert, der unsere Anfangszeit darstellt, und einen timePassed-Wert, der angibt, wie viel Zeit vergangen ist, sobald der Countdown beginnt.
Was wir tun müssen, ist, den Wert von timePassed pro Sekunde um eins zu erhöhen und den timeLeft-Wert basierend auf dem neuen timePassed-Wert neu zu berechnen. Dies können wir mit der setInterval-Funktion erreichen.
Lassen Sie uns eine Methode namens startTimer implementieren, die
- das Zählerintervall festlegt
- den Wert
timePassedjede Sekunde inkrementiert - den neuen Wert von
timeLeftneu berechnet - den Wert des Labels in der Vorlage aktualisiert
Wir müssen auch die Referenz auf dieses Intervallobjekt behalten, um es bei Bedarf zu löschen – deshalb erstellen wir eine timerInterval-Variable.
let timerInterval = null;
document.getElementById("app").innerHTML = `...`
function startTimer() {
timerInterval = setInterval(() => {
// The amount of time passed increments by one
timePassed = timePassed += 1;
timeLeft = TIME_LIMIT - timePassed;
// The time left label is updated
document.getElementById("base-timer-label").innerHTML = formatTime(timeLeft);
}, 1000);
}
Wir haben eine Methode, die den Timer startet, aber wir rufen sie nirgends auf. Lassen Sie uns unseren Timer sofort beim Laden starten.
document.getElementById("app").innerHTML = `...`
startTimer();
Das war's! Unser Timer zählt jetzt die Zeit herunter. Das ist zwar schön und gut, aber es wäre schöner, wenn wir dem Ring um das Zeit-Label etwas Farbe hinzufügen und die Farbe bei verschiedenen Zeitwerten ändern könnten.

Schritt 4: Den Timerring mit einem weiteren Ring überdecken
Um die verstrichene Zeit zu visualisieren, müssen wir unserem Ring eine zweite Ebene hinzufügen, die die Animation steuert. Wir stapeln im Wesentlichen einen neuen grünen Ring über dem ursprünglichen grauen Ring, sodass der grüne Ring im Laufe der Zeit animiert wird, um den grauen Ring freizulegen, wie eine Fortschrittsanzeige.
Fügen wir zunächst ein path-Element in unser SVG-Element ein.
document.getElementById("app").innerHTML = `
<div class="base-timer">
<svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<g class="base-timer__circle">
<circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle>
<path
id="base-timer-path-remaining"
stroke-dasharray="283"
class="base-timer__path-remaining ${remainingPathColor}"
d="
M 50, 50
m -45, 0
a 45,45 0 1,0 90,0
a 45,45 0 1,0 -90,0
"
></path>
</g>
</svg>
<span id="base-timer-label" class="base-timer__label">
${formatTime(timeLeft)}
</span>
</div>
`;
Als Nächstes erstellen wir eine Anfangsfarbe für den Pfad der verbleibenden Zeit.
const COLOR_CODES = {
info: {
color: "green"
}
};
let remainingPathColor = COLOR_CODES.info.color;
Schließlich fügen wir ein paar Stile hinzu, damit der kreisförmige Pfad wie unser ursprünglicher grauer Ring aussieht. Wichtig ist hier, dass die stroke-width die gleiche Größe wie der ursprüngliche Ring hat und die Dauer der transition auf eine Sekunde eingestellt ist, damit sie reibungslos animiert wird und mit der verbleibenden Zeit im Zeit-Label übereinstimmt.
.base-timer__path-remaining {
/* Just as thick as the original ring */
stroke-width: 7px;
/* Rounds the line endings to create a seamless circle */
stroke-linecap: round;
/* Makes sure the animation starts at the top of the circle */
transform: rotate(90deg);
transform-origin: center;
/* One second aligns with the speed of the countdown timer */
transition: 1s linear all;
/* Allows the ring to change color when the color value updates */
stroke: currentColor;
}
.base-timer__svg {
/* Flips the svg and makes the animation to move left-to-right */
transform: scaleX(-1);
}
Dies erzeugt einen Konturstrich, der den Timerring wie erwartet abdeckt, aber er animiert sich noch nicht, um den Timerring freizulegen, während die Zeit vergeht.

Um die Länge der Linie der verbleibenden Zeit zu animieren, werden wir die Eigenschaft stroke-dasharray verwenden. Chris erklärt, wie sie verwendet wird, um die Illusion zu erwecken, dass sich ein Element selbst "zeichnet". Und es gibt weitere Details zur Eigenschaft und Beispiele dazu im CSS-Tricks-Almanach.
Schritt 5: Animieren des Fortschrittrings
Sehen wir uns an, wie unser Ring mit verschiedenen stroke-dasharray-Werten aussehen wird

Was wir sehen, ist, dass der Wert von stroke-dasharray unseren Ring für die verbleibende Zeit tatsächlich in gleich lange Abschnitte unterteilt, wobei die Länge dem Wert der verbleibenden Zeit entspricht. Das passiert, wenn wir den Wert von stroke-dasharray auf eine einstellige Zahl (d. h. 1-9) setzen.
Der Name dasharray deutet darauf hin, dass wir mehrere Werte als Array angeben können. Sehen wir uns an, wie es sich verhält, wenn wir stattdessen zwei Zahlen statt einer angeben; in diesem Fall sind diese Werte 10 und 30.

stroke-dasharray: 10 30Das setzt die Länge des ersten Abschnitts (verbleibende Zeit) auf 10 und die Länge des zweiten Abschnitts (verstrichene Zeit) auf 30. Das können wir mit einem kleinen Trick in unserem Timer verwenden. Was wir anfangs brauchen, ist, dass der Ring die volle Länge des Kreises abdeckt, d. h. die verbleibende Zeit entspricht der Länge unseres Rings.
Was ist das für eine Länge? Holen Sie Ihr altes Geometriebuch hervor, denn wir können die Länge eines Bogens mit etwas Mathematik berechnen
Length = 2πr = 2 * π * 45 = 282,6
Das ist der Wert, den wir verwenden wollen, wenn der Ring anfänglich geladen wird. Sehen wir uns an, wie es aussieht.

stroke-dasharray: 283 283Das funktioniert!
OK, der erste Wert im Array ist unsere verbleibende Zeit, und der zweite markiert, wie viel Zeit vergangen ist. Was wir jetzt tun müssen, ist, den ersten Wert zu manipulieren. Sehen wir uns unten an, was wir erwarten können, wenn wir den ersten Wert ändern.

Wir werden zwei Methoden erstellen, eine, die dafür verantwortlich ist, zu berechnen, welcher Bruchteil der Anfangszeit noch übrig ist, und eine, die dafür verantwortlich ist, den stroke-dasharray-Wert zu berechnen und das <path>-Element zu aktualisieren, das unsere verbleibende Zeit darstellt.
// Divides time left by the defined time limit.
function calculateTimeFraction() {
return timeLeft / TIME_LIMIT;
}
// Update the dasharray value as time passes, starting with 283
function setCircleDasharray() {
const circleDasharray = `${(
calculateTimeFraction() * FULL_DASH_ARRAY
).toFixed(0)} 283`;
document
.getElementById("base-timer-path-remaining")
.setAttribute("stroke-dasharray", circleDasharray);
}
Wir müssen auch unseren Pfad jede Sekunde aktualisieren. Das bedeutet, dass wir die neu erstellte Methode setCircleDasharray innerhalb unseres timerInterval aufrufen müssen.
function startTimer() {
timerInterval = setInterval(() => {
timePassed = timePassed += 1;
timeLeft = TIME_LIMIT - timePassed;
document.getElementById("base-timer-label").innerHTML = formatTime(timeLeft);
setCircleDasharray();
}, 1000);
}
Jetzt können wir sehen, wie sich die Dinge bewegen!

Juhu, es funktioniert... aber... schauen Sie genau hin, besonders am Ende. Es sieht so aus, als ob unsere Animation eine Sekunde hinterherhinkt. Wenn wir 0 erreichen, ist noch ein kleines Stück des Rings sichtbar.

Das liegt an der Animationsdauer von einer Sekunde. Wenn der Wert der verbleibenden Zeit auf Null gesetzt wird, dauert es immer noch eine Sekunde, bis der Ring tatsächlich auf Null animiert ist. Wir können das loswerden, indem wir die Länge des Rings während des Countdowns schrittweise reduzieren. Das machen wir in unserer Methode calculateTimeFraction.
function calculateTimeFraction() {
const rawTimeFraction = timeLeft / TIME_LIMIT;
return rawTimeFraction - (1 / TIME_LIMIT) * (1 - rawTimeFraction);
}
Da haben wir es!

Hoppla... da ist noch eine Sache. Wir haben gesagt, dass wir die Farbe der Fortschrittsanzeige ändern wollen, wenn die verbleibende Zeit bestimmte Punkte erreicht – so ähnlich wie ein Hinweis an den Benutzer, dass die Zeit fast abgelaufen ist.
Schritt 6: Ändern Sie die Fortschrittsfarbe an bestimmten Zeitpunkten
Zuerst müssen wir zwei Schwellenwerte hinzufügen, die anzeigen, wann wir in den Warn- und Alarmzustand wechseln und Farben für jeden dieser Zustände hinzufügen. Wir beginnen mit Grün, wechseln dann zu Orange als Warnung und dann zu Rot, wenn die Zeit fast abgelaufen ist.
// Warning occurs at 10s
const WARNING_THRESHOLD = 10;
// Alert occurs at 5s
const ALERT_THRESHOLD = 5;
const COLOR_CODES = {
info: {
color: "green"
},
warning: {
color: "orange",
threshold: WARNING_THRESHOLD
},
alert: {
color: "red",
threshold: ALERT_THRESHOLD
}
};
Erstellen wir nun eine Methode, die dafür verantwortlich ist zu prüfen, ob der Schwellenwert überschritten wurde und die Fortschrittsfarbe zu ändern, wenn dies geschieht.
function setRemainingPathColor(timeLeft) {
const { alert, warning, info } = COLOR_CODES;
// If the remaining time is less than or equal to 5, remove the "warning" class and apply the "alert" class.
if (timeLeft <= alert.threshold) {
document
.getElementById("base-timer-path-remaining")
.classList.remove(warning.color);
document
.getElementById("base-timer-path-remaining")
.classList.add(alert.color);
// If the remaining time is less than or equal to 10, remove the base color and apply the "warning" class.
} else if (timeLeft <= warning.threshold) {
document
.getElementById("base-timer-path-remaining")
.classList.remove(info.color);
document
.getElementById("base-timer-path-remaining")
.classList.add(warning.color);
}
}
Wir entfernen also im Grunde eine CSS-Klasse, wenn der Timer einen Punkt erreicht, und fügen stattdessen eine andere hinzu. Wir müssen diese Klassen definieren.
.base-timer__path-remaining.green {
color: rgb(65, 184, 131);
}
.base-timer__path-remaining.orange {
color: orange;
}
.base-timer__path-remaining.red {
color: red;
}
Voilà, da haben wir es. Hier ist die Demo wieder mit allem zusammen.
Verwandt… Chen Hui Jings Kannst du einen Countdown-Timer in reinem CSS erstellen?
Hier ist eine weitere schnelle reine CSS-Version basierend auf der in diesem Artikel.
Änderungen
Vereinfachte das SVG, indem beide Elemente Kreise um den
0,0-Punkt gemacht wurden (das Einstellen desviewBoxauf-50 -50 100 100setzt den Ursprung0,0genau in die Mitte des SVG und wir müssencxundcynicht mehr setzen, da beide standardmäßig auf0gesetzt sind)Animierte sowohl
stroke-dashoffsetals auchstrokemitlinear-Keyframe-AnimationenAnimierte den Timer-Inhalt mit einer
steps()-Keyframe-Animation (dieser Teil kann stark vereinfacht werden, wenn wir Houdini verwenden würden, aber dann auf Kosten der Unterstützung, also weiß ich nicht)Es wäre toll, wenn es einen Button drücken oder automatisch zu einer bestimmten URL gehen würde, wenn es fertig ist.
Wie macht man den Timer, dass er sich stattdessen füllt? Ich möchte dieses Beispiel verwenden, um einen kleinen Musikplayer zu machen
Sie können
calculateTimeFraction() * FULL_DASH_ARRAYdurchFULL_DASH_ARRAY - (calculateTimeFraction() * FULL_DASH_ARRAY)ersetzen.Wie ändern wir die Richtung hierfür?
Schön! Ich habe ein wenig mit Timern herumgespielt. Nur eine Kleinigkeit, falls jemand interessiert ist, Sie
können die if-Anweisung umgehen, um die Zeit <10 mit einer führenden 0 anzuzeigen, indem Sie padStart verwenden. So
return
${minutes}:${seconds.toString().padStart(2, '0')};Ich dachte, dies wäre ein guter Kandidat für die Umwandlung in ein benutzerdefiniertes Element, also habe ich es versucht. Ich bin *fast* fertig, aber was zum Teufel
https://codepen.io/amundo/pen/PoqYWEK
Das war ein wirklich cooler, gut geschriebener Artikel!
Es gibt immer noch einen kleinen Fehler, nämlich dass der 'Ring' erst zu bewegen scheint, wenn der Countdown 00:19 erreicht.
Ist es möglich, den Timer in Bezug auf Arbeitstage zu berechnen?
Ich würde eine kleine Überarbeitung des Tutorials empfehlen, da derzeit einige Schritte fehlen, wenn man ihm folgt – z. B. die Bedingung aus der Funktion startTimer() (damit der Timer nie stoppt und in negative Werte läuft) oder das Setzen der FULL_DASH_ARRAY-Konstante.
Diese Countdown-Uhr ist wunderschön und perfekt für ein Billardspiel.
Ich müsste starten, stoppen/pausieren, fortsetzen können in dem, was ich Lags genannt habe, jeder Start und Stopp meldet die Zeitdauer und gibt die Restzeit an. Das ist cool, die Möglichkeiten…
Das sieht toll aus! Wie schwer wäre es, es anzupassen, damit es anzeigt
Stunden:Minuten:Sekunden
// Minuten duplizieren und als Stunden bezeichnen. Beim Zurückgeben den Stundenwert hinzufügen. Fertig!
function formatTime(time) {
const hours = Math.floor(time / 60);
const minutes = Math.floor(time / 60);
let seconds = time % 60;
if (seconds < 10) {
seconds =
0${seconds};}
return
${hours}:${minutes}:${seconds};}
Der Methodenname in Schritt 2 stimmt nicht mit dem Methodennamen in der Vorlage in Schritt 2 überein.
formatTimeLeft / formatTime
Tolles Tutorial!
Hallo, ich liebe diesen Timer
Ich brauche mehrere Timer auf einer Seite, und jeder soll auf einen Klick reagieren.
Als ich es versuchte, erhielt ich einen JS-Fehler „FULL_DASH_ARRAY“ wurde bereits deklariert, und die anderen Konstanten VARS, die ich hinzugefügt habe, _1, _2, _3 usw. zu jeder VAR in jeder Timer-Instanz „FULL_DASH_ARRAY_1“, aber es gab das gleiche Problem.
Irgendwelche Ideen?
Auf IE8 funktioniert diese Animation irgendwie nicht. Bitte um Vorschlag
Tolle Sache. Ich liebe es.
Was muss ich tun, um die Minuten und den : zu entfernen, wenn weniger als eine Minute übrig ist?
Außerdem, wie könnte ich es so machen, dass es einen Text anzeigt, wenn der Countdown 00 erreicht?
Großartige Anleitung! Ich benutze sie in meinem Projekt. Es gibt jedoch einen kleinen Fehler, den ich nicht lösen kann. Der Kreis beginnt erst nach ein oder zwei Sekunden zu bewegen. Wenn er 10 Sekunden erreicht, sollte der Kreis in der Mitte sein.
Ich habe herausgefunden, dass die Funktion calculateTimeFraction beim ersten Berechnen des dashArray die Parameter 19, 20 erhält. Ich dachte jedoch, dass sie '20,20' verwenden sollte, da der Timer gerade erst begonnen hat.
const circleDashArray =
${(;calculateTimeFraction(timeLeft, timeLimit) * FULL_DASH_ARRAY
).toFixed(0)} 283
Wie kann ich dieses Verhalten mit React Hooks erreichen?
Ich hoffe, das hilft jemandem. Wie Sie vielleicht bemerken, beginnt die Animation nach 1 Sekunde zu laufen (im letzten Beispiel beginnt sie bei 19 Sek.). Der Grund dafür ist das Intervall von 1 Sek. Die Anwendung wartet 1 Sekunde und beginnt dann zu laufen.
Um dies zu lösen, starten Sie die Animation, bevor Sie das Intervall festlegen. Beispiel
Außerdem benötigen Sie die Korrektur bei der Berechnung des Zeitanteils nicht. Wenn Sie es als timeLeft / TIME_LIMIT belassen, funktioniert es perfekt.
Danke, ich habe diesen Artikel wirklich geliebt und viel daraus gelernt!
Das ist wunderschön und perfekt für das, was ich für einen Fitnessstudio-Timer tun möchte.
Gibt es Gedanken zur Richtung, wie man eine "Fernbedienung" dafür mit einem Handy integrieren kann?
Ich bin neu(er) in der Programmierung, aber mein erster Gedanke wäre etwas wie ein WebSocket zu einem Server, um zu prüfen, ob der Timer zurückgesetzt, gestoppt usw. ist. Sollte ich diesen Weg für diese Aufgabe in Betracht ziehen? Ich suche nur nach einer Richtung für die Recherche.
Hallo,
Danke für die Schritt-für-Schritt-Anleitung! Ich frage mich, warum „r“ in der Formel 2 * pi * r gleich 45 ist?
Die Breite und Höhe des Kreises beträgt 300 Pixel, also gehe ich davon aus, dass der Radius 150 (die Hälfte des Durchmessers) sein sollte?
Bitte, erklären Sie das jemandem. Danke!
Hallo, aus irgendeinem Grund scheint es nicht zu funktionieren... erstens ist FULL_DASH_ARRAY nicht definiert und die Animation funktioniert auch nicht.
Ist es nach dem Countdown möglich, einen Download-Button hinzuzufügen, der angezeigt wird, wenn er Null erreicht?
Das Tutorial lässt die Definition von FULL_DASH_ARRAY vermissen, sodass sich der grüne Fortschritt nie bewegt.
Basierend auf der Einstellung von stroke-dasharray habe ich die Variable erstellt (platziert in der Zeile unter der anderen Konstanten TIME_LIMIT).
Wenn Sie dieses Problem haben, müssen Sie Folgendes zu Ihrem JS hinzufügen
const FULL_DASH_ARRAY = 283;
Hallo,
Danke für das Skript.
Nachdem ich NULL erreicht habe, wie kann ich es zurücksetzen, wenn ich die Funktion startTimer() erneut aufrufe? Danke
Gruß, Nuno