Cómo construí mi aplicación Pomodoro Clock y las lecciones que aprendí en el camino

Me embarqué en mi viaje de freeCodeCamp en diciembre de 2017 y me faltan dos proyectos para completar el Certificado de Desarrollo de Front-End. Esta publicación documenta mi proceso para completar el proyecto del Reloj Pomodoro.

¿Qué es un Reloj Pomodoro?

La Técnica Pomodoro es un marco de gestión del tiempo que es tan simple como efectivo: usa un temporizador para dividir su trabajo en bloques de tiempo (generalmente 25 minutos), separados por un descanso de 5 minutos. Después de cada 4 pomodoros, puede tomar un descanso más largo.

Tuve que cumplir las siguientes historias de usuario:

  • Puedo iniciar un pomodoro de 25 minutos, y el temporizador se apagará una vez que hayan transcurrido 25 minutos.
  • Puedo poner a cero el reloj para mi próximo pomodoro.
  • Puedo personalizar la longitud de cada pomodoro.

Patrón de diseñó

Mi principio de diseño es mantener la interfaz de usuario limpia y simple. Me encantó la idea de usar un tomate como temporizador. Hay una pantalla de trabajo / descanso, cuenta atrás del temporizador y un botón de reproducción / pausa.

Debajo del temporizador, tenía configuraciones para modificar el trabajo y la duración del descanso, y un botón de reinicio.

Problemas de diseño que encontré

Tuve problemas importantes para colocar la imagen del tomate en el fondo debajo de los otros elementos. ¡Cómo me gustaría que hubiera una opción de diseño que pudiera seleccionar! ?

Una sugerencia que encontré fue guardar la imagen de tomate en mi color de fondo preferido como una nueva imagen, luego usar esa imagen en el fondo. La desventaja de eso fue que comenzó a verse inestable una vez que probé la capacidad de respuesta del diseño.

Al final, he conseguido hacerlo bien con una combinación de absolute positioning, la modificación de las topy los leftporcentajes, y transform.

#status { position: absolute; top: 45%; left:50%; transform: translate(-50%, -50%);}
.timerDisplay { position: absolute; top: 60%; left: 50%; transform: translate(-50%, -50%);}
#start-btn { position: absolute; bottom: 8%; left: 48%; transform: translate(-50%, -50%);}

Los ajustes inferiores fueron bastante sencillos. Usé CSS Grid para separar los componentes en tres columnas, siendo la columna del medio la mitad del ancho de las columnas externas.

.settings { margin: auto; width: 80%; display: grid; grid-template-columns: 2fr 1fr 2fr; align-items: center;}

Una vez más, solía transformcambiar el botón de reinicio para una mejor alineación.

Estructurar mi código y luego desgarrarlo

Encuentro útil idear la estructura de mi código si analizo los requisitos:

  • El temporizador alternará entre el inicio y la pausa cuando haga clic en el botón "Inicio".
  • Una vez que el temporizador llega a cero, sonará una alarma.
  • Una sesión de trabajo siempre va seguida de una sesión de descanso.
  • Se pueden modificar las duraciones de trabajo y descanso.
  • El botón 'reiniciar' (lo adivinó) reiniciará el temporizador.

Anteriormente había completado un reloj de cuenta regresiva en el curso Wes Bos JavaScript30, así que sabía que podía usar el setIntervalmétodo. También decidí desafiarme a mí mismo al ceñirme a JavaScript vanilla y evitar depender de jQuery.

Y entonces comencé a escribir mi código JavaScript. Aunque logré crear un reloj pomodoro funcional, no revisaré la primera versión de mi código aquí. Esto se debe a que hice cambios significativos después de recibir comentarios constructivos de un extraño increíble en Reddit. ?

Sí, ¡pasan cosas bonitas en Reddit!

Los principales puntos de la retroalimentación fueron:

  • setInterval(timer, 1000)tarda al menos 1000 ms en activarse, pero puede tardar más. Por lo tanto, debe verificar cuánto tiempo realmente transcurrió, o su reloj puede ser inexacto.
  • Agrupe todas las actualizaciones de HTML en una sección, ya que esto hace que su código sea más fácil de actualizar y depurar.
  • Generalmente es una buena idea hacer el código sin pensar en la representación en absoluto.
  • Asegúrese de la lógica del temporizador y elimine el código innecesario.
  • Asegúrese de que los nombres de las variables sean descriptivos. Deje comentarios cuando sea necesario.

Puedes ver mi primera confirmación en GitHub.

Refactorizando mi código

Después de recibir todos esos valiosos comentarios, refactorice mi código varias veces hasta que estuve satisfecho con él.

Primero, definí todas las variables. Como no estaba usando jQuery, me aseguré de capturar todos mis elementos usando document.querySelector.

let countdown = 0; // variable to set/clear intervalslet seconds = 1500; // seconds left on the clocklet workTime = 25;let breakTime = 5;let isBreak = true;let isPaused = true;
const status = document.querySelector("#status");const timerDisplay = document.querySelector(".timerDisplay");const startBtn = document.querySelector("#start-btn");const resetBtn = document.querySelector("#reset");const workMin = document.querySelector("#work-min");const breakMin = document.querySelector("#break-min");

A continuación, creé el elemento de audio.

const alarm = document.createElement('audio'); alarm.setAttribute("src", "//www.soundjay.com/misc/sounds/bell-ringing-05.mp3");

Cuando se hace clic en el botón 'iniciar', se borra el intervalo. Se establece un nuevo intervalo si isPausedcambia de verdadero a falso .

El botón 'reiniciar' borra el intervalo y reinicia las variables.

startBtn.addEventListener('click', () => { clearInterval(countdown); isPaused = !isPaused; if (!isPaused) { countdown = setInterval(timer, 1000); }})
resetBtn.addEventListener('click', () => { clearInterval(countdown); seconds = workTime * 60; countdown = 0; isPaused = true; isBreak = true;})

The timer function is where the countdown magic happens. It deducts one second from seconds. If seconds <; 0, the alarm is played, and the function determines if the next countdown should be a work session or break session.

function timer() { seconds --; if (seconds < 0) { clearInterval(countdown); alarm.currentTime = 0; alarm.play(); seconds = (isBreak ? breakTime : workTime) * 60; isBreak = !isBreak; }}

Now it’s time to work on the +/- buttons for the work and break durations. Initially, I created an onclick function for every button. While it was functional, there was definitely room for improvement.

document.querySelector("#work-plus").onclick = function() { workDuration  5 ? workDuration -= increment : workDuration; }document.querySelector("#break-plus").onclick = function() { breakDuration  5 ? breakDuration -= increment : breakDuration; }

That same kind Redditor suggested that I use an associative array, which is essentially a set of key value pairs.

let incrementFunctions = {"#work-plus": function () { workTime = Math.min(workTime + increment, 60)}, "#work-minus": function () { workTime = Math.max(workTime - increment, 5)}, "#break-plus": function () { breakTime = Math.min(breakTime + increment, 60)}, "#break-minus": function () { breakTime = Math.max(breakTime - increment, 5)}};
for (var key in incrementFunctions) { if (incrementFunctions.hasOwnProperty(key)) { document.querySelector(key).onclick = incrementFunctions[key]; }}

It’s time to update the HTML!

I created functions to update the countdown display and button display, and incorporated those functions into an overarching function that also updated the Work/Break status and durations.

Finally, I used document.onclick to run the updateHTML function everytime the user clicks on the page. I also used window.setInterval to run the function 10 times a second for good measure.

function countdownDisplay() { let minutes = Math.floor(seconds / 60); let remainderSeconds = seconds % 60; timerDisplay.textContent = `${minutes}:${remainderSeconds < 10 ? '0' : ''}${remainderSeconds}`;}
function buttonDisplay() { if (isPaused && countdown === 0) { startBtn.textContent = "START"; } else if (isPaused && countdown !== 0) { startBtn.textContent = "Continue"; } else { startBtn.textContent = "Pause"; }}
function updateHTML() { countdownDisplay(); buttonDisplay(); isBreak ? status.textContent = "Keep Working" : status.textContent = "Take a Break!"; workMin.textContent = workTime; breakMin.textContent = breakTime;}
window.setInterval(updateHTML, 100);
document.onclick = updateHTML;

And that’s the wrap up of my project!

You can view my final project here.

Final thoughts

My biggest takeaway from this project is that I should aim for simplicity in terms of code design, because it is a prerequisite for reliability. It will make my code easy to understand, easy to debug, and easy to update.

I am also reminded of the benefits of paired programming and code reviews, especially when one is new to coding.

There is still so much to learn. But for now, let me reward myself with a plate of Pasta al pomodoro.