Cómo crear un temporizador de cuenta regresiva animado con HTML, CSS y JavaScript

Índice
  1. Paso 1: comience con el marcado y los estilos básicos
  2. Paso 2: configurar la etiqueta de tiempo
  3. Paso 3: cuenta regresiva
  4. Paso 4: cubre el anillo del temporizador con otro anillo
  5. Paso 5: anima el anillo de progreso
  6. Paso 6: cambia el color del progreso en determinados momentos

¿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:SSformato.
  • 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 #appelemento. 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:SSformato. 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 timeLeftvalor, 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 timeLimitvalor que representa nuestro tiempo inicial y un timePassedvalor que indica cuánto tiempo ha pasado una vez que comienza la cuenta regresiva.

Lo que debemos hacer es aumentar el valor de timePasseden una unidad por segundo y volver a calcular el timeLeftvalor en función del nuevo timePassedvalor. Podemos lograrlo usando la setIntervalfunción.

Implementamos un método llamado startTimerque:

  • Establecer intervalo de contador
  • Incrementar el timePassedvalor cada segundo.
  • Vuelva a calcular el nuevo valor detimeLeft
  • 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 timerIntervalvariable.

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-widthtenga el mismo tamaño que el anillo original y que la duración del mismo transitionesté 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-dasharraypropiedad. 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-dasharrayvalores:

Lo que podemos ver es que el valor de stroke-dasharrayen 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-dasharrayen 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-dasharrayvalor y actualizar el pathelemento 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 setCircleDasharraymé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 calculateTimeFractionmé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.

SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir

Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion