Cómo crear un temporizador de cuenta regresiva animado con HTML, CSS y JavaScript
¿Alguna vez ha necesitado un temporizador de cuenta regresiva para un proyecto? Para algo así, puede ser natural recurrir a un complemento, pero en realidad es mucho más sencillo crear uno de lo que piensas y solo requiere la trifecta de HTML, CSS y JavaScript. ¡Hagamos uno juntos!
Esto es lo que prevemos:
Aquí hay algunas cosas que hace el temporizador y que cubriremos en esta publicación:
- Muestra el tiempo inicial restante
- Convierte el valor de tiempo a un
MM:SS
formato. - Calcula la diferencia entre el tiempo restante inicial y el tiempo transcurrido
- Cambia de color a medida que el tiempo restante se acerca a cero
- Muestra el progreso del tiempo restante como un anillo animado.
Bien, eso es lo que queremos, ¡así que hagámoslo realidad!
Paso 1: comience con el marcado y los estilos básicos
Comenzamos a crear una plantilla básica para nuestro temporizador. Agregaremos un svg con un elemento circular dentro para dibujar un anillo de temporizador que indicará el tiempo que pasará y agregaremos un lapso para mostrar el valor del tiempo restante. Tenga en cuenta que estamos escribiendo el HTML en JavaScript y lo inyectamos en el DOM apuntando al #app
elemento. Claro, podríamos mover gran parte de él a un archivo HTML, si eso es lo que más te gusta.
document.getElementById("app").innerHTML = `div svg viewBox="0 0 100 100" g circle cx="50" cy="50" r="45" / /g /svg span !-- Remaining time label -- /span/div`;
Ahora que tenemos algunas marcas con las que trabajar, le daremos un poco de estilo para tener una buena imagen con la que empezar. Específicamente, vamos a:
- Establecer el tamaño del temporizador
- Elimine el relleno y el trazo del elemento envolvente del círculo para obtener la forma, pero deje que se vea el tiempo transcurrido.
- Establecer el ancho y el color del anillo.
/* 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;}
Una vez hecho esto, terminamos con una plantilla básica que se ve así.
Paso 2: configurar la etiqueta de tiempo
Como probablemente habrás notado, la plantilla incluye un espacio vacío que contendrá el tiempo restante. Llenaremos ese lugar con un valor adecuado. Dijimos anteriormente que la hora estará en MM:SS
formato. Para ello crearemos un método llamado 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}`;}
Luego usaremos nuestro método en la plantilla:
document.getElementById("app").innerHTML = `div svg viewBox="0 0 100 100" g circle cx="50" cy="50" r="45"/circle /g /svg span ${formatTime(timeLeft)} /span/div`
Para mostrar el valor dentro del anillo necesitamos actualizar un poco nuestros estilos.
.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;}
Bien, estamos listos para jugar con el timeLeft
valor, pero el valor aún no existe. Creémoslo y establecimos el valor inicial en nuestro límite de tiempo.
// Start with an initial value of 20 secondsconst TIME_LIMIT = 20;// Initially, no time has passed, but this will count up// and subtract from the TIME_LIMITlet timePassed = 0;let timeLeft = TIME_LIMIT;
Y estamos un paso más cerca.
¡Tocar el asunto exacto! Ahora tenemos un cronómetro que comienza a los 20 segundos… pero todavía no cuenta. Démosle vida para que cuente hasta cero segundos.
Paso 3: cuenta regresiva
Pensemos en lo que necesitamos para contar el tiempo. En este momento, tenemos un timeLimit
valor que representa nuestro tiempo inicial y un timePassed
valor que indica cuánto tiempo ha pasado una vez que comienza la cuenta regresiva.
Lo que debemos hacer es aumentar el valor de timePassed
en una unidad por segundo y volver a calcular el timeLeft
valor en función del nuevo timePassed
valor. Podemos lograrlo usando la setInterval
función.
Implementamos un método llamado startTimer
que:
- Establecer intervalo de contador
- Incrementar el
timePassed
valor cada segundo. - Vuelva a calcular el nuevo valor de
timeLeft
- Actualizar el valor de la etiqueta en la plantilla.
También necesitamos mantener la referencia a ese objeto de intervalo para borrarlo cuando sea necesario; es por eso que crearemos una 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);}
Tenemos un método que inicia el temporizador pero no lo llamamos en ningún lado. Iniciamos nuestro cronómetro inmediatamente después de la carga.
document.getElementById("app").innerHTML = `...`startTimer();
¡Eso es todo! Nuestro cronómetro ahora contará el tiempo. Si bien eso es genial, sería mejor si pudiéramos agregar algo de color al anillo alrededor de la etiqueta de hora y cambiar el color en diferentes valores de hora.
Paso 4: cubre el anillo del temporizador con otro anillo
Para visualizar el paso del tiempo, necesitamos agregar una segunda capa a nuestro anillo que maneja la animación. Básicamente, lo que estamos haciendo es apilar un nuevo anillo verde encima del anillo gris original para que el anillo verde se anime y revele el anillo gris a medida que pasa el tiempo, como una barra de progreso.
Primero agregamos un elemento de ruta en nuestro elemento SVG.
document.getElementById("app").innerHTML = `div svg viewBox="0 0 100 100" g circle cx="50" cy="50" r="45"/circle path stroke-dasharray="283" token interpolation"${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 ${formatTime(timeLeft)} /span/div`;
A continuación, creemos un color inicial para la ruta de tiempo restante.
const COLOR_CODES = { info: { color: "green" }};let remainingPathColor = COLOR_CODES.info.color;
Finalmente, agreguemos algunos estilos para que el trazado circular se parezca a nuestro anillo gris original. Lo importante aquí es asegurarse de que stroke-width
tenga el mismo tamaño que el anillo original y que la duración del mismo transition
esté establecida en un segundo para que se anime suavemente y se corresponda con el tiempo restante en la etiqueta de tiempo.
.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);}
Esto generará un trazo que cubre el anillo del temporizador como debería, pero aún no se anima para revelar el anillo del temporizador a medida que pasa el tiempo.
Para animar la duración de la línea de tiempo restante, usaremos la stroke-dasharray
propiedad. Chris explica cómo se utiliza para crear la ilusión de un elemento que se “dibuja” a sí mismo. Y hay más detalles sobre la propiedad y ejemplos de ella en el almanaque de CSS-Tricks.
Paso 5: anima el anillo de progreso
Veamos cómo quedará nuestro anillo con diferentes stroke-dasharray
valores:
Lo que podemos ver es que el valor de stroke-dasharray
en realidad está cortando nuestro anillo de tiempo restante en secciones de igual longitud, donde la longitud es el valor del tiempo restante. Esto sucede cuando establecemos el valor de stroke-dasharray
en un número de un solo dígito (es decir, 1-9).
El nombre dasharray sugiere que podemos establecer múltiples valores como una matriz. Veamos cómo se comportará si configuramos dos números en lugar de uno; en este caso, esos valores son 10 y 30.
Eso establece la duración de la primera sección (tiempo restante) en 10 y la segunda sección (tiempo transcurrido) en 30. Podemos usar eso en nuestro temporizador con un pequeño truco. Lo que necesitamos inicialmente es que el anillo cubra toda la longitud del círculo, es decir, el tiempo restante es igual a la longitud de nuestro anillo.
¿Cuál es esa longitud? Saca tu viejo libro de texto de geometría, porque podemos calcular la longitud de un arco con algunas matemáticas:
Length = 2πr = 2 * π * 45 = 282,6
Ese es el valor que queremos usar cuando montamos el anillo inicialmente. Veamos cómo se ve.
¡Eso funciona!
Bien, el primer valor de la matriz es el tiempo restante y el segundo marca cuánto tiempo ha pasado. Lo que debemos hacer ahora es manipular el primer valor. Veamos a continuación qué podemos esperar cuando cambiamos el primer valor.
Crearemos dos métodos, uno responsable de calcular qué fracción del tiempo inicial queda y otro responsable de calcular el stroke-dasharray
valor y actualizar el path
elemento que representa nuestro tiempo restante.
// Divides time left by the defined time limit.function calculateTimeFraction() { return timeLeft / TIME_LIMIT;} // Update the dasharray value as time passes, starting with 283function setCircleDasharray() { const circleDasharray = `${( calculateTimeFraction() * FULL_DASH_ARRAY ).toFixed(0)} 283`; document .getElementById("base-timer-path-remaining") .setAttribute("stroke-dasharray", circleDasharray);}
También necesitamos actualizar nuestra ruta cada segundo que pasa. Eso significa que debemos llamar al setCircleDasharray
método recién creado dentro de nuestro archivo timerInterval
.
function startTimer() { timerInterval = setInterval(() = { timePassed = timePassed += 1; timeLeft = TIME_LIMIT - timePassed; document.getElementById("base-timer-label").innerHTML = formatTime(timeLeft); setCircleDasharray(); }, 1000);}
¡Ahora podemos ver las cosas moverse!
Woohoo, funciona… pero… fíjate bien, especialmente al final. Parece que nuestra animación tiene un retraso de un segundo. Cuando llegamos a 0, todavía se ve un pequeño trozo del anillo.
Esto se debe a que la duración de la animación está establecida en un segundo. Cuando el valor del tiempo restante se establece en cero, todavía se necesita un segundo para animar el anillo a cero. Podemos deshacernos de eso reduciendo la longitud del anillo gradualmente durante la cuenta regresiva. Hacemos eso en nuestro calculateTimeFraction
método.
function calculateTimeFraction() { const rawTimeFraction = timeLeft / TIME_LIMIT; return rawTimeFraction - (1 / TIME_LIMIT) * (1 - rawTimeFraction);}
¡Aquí vamos!
Ups… hay una cosa más. Dijimos que queríamos cambiar el color del indicador de progreso cuando el tiempo restante llega a ciertos puntos, algo así como informarle al usuario que el tiempo casi se acaba.
Paso 6: cambia el color del progreso en determinados momentos
Primero, debemos agregar dos umbrales que indicarán cuándo debemos cambiar a los estados de advertencia y alerta y agregar colores para cada uno de esos estados. Comenzamos con verde, luego pasamos a naranja como advertencia, seguido de rojo cuando el tiempo casi se acaba.
// Warning occurs at 10sconst WARNING_THRESHOLD = 10;// Alert occurs at 5sconst ALERT_THRESHOLD = 5;const COLOR_CODES = { info: { color: "green" }, warning: { color: "orange", threshold: WARNING_THRESHOLD }, alert: { color: "red", threshold: ALERT_THRESHOLD }};
Ahora, creemos un método que sea responsable de verificar si se excedió el umbral y cambiar el color del progreso cuando eso suceda.
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); }}
Entonces, básicamente eliminamos una clase CSS cuando el temporizador llega a un punto y agregamos otra en su lugar. Necesitaremos definir esas clases.
.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à, ahí lo tenemos. Aquí está la demostración nuevamente con todo junto.
Deja un comentario