Learning Vue Part 3: Building a pomodoro timer

WHAT TO KNOW - Sep 9 - - Dev Community

<!DOCTYPE html>





Learning Vue Part 3: Building a Pomodoro Timer

<br> body {<br> font-family: sans-serif;<br> margin: 0;<br> padding: 20px;<br> }<br> h1, h2, h3 {<br> margin-top: 30px;<br> }<br> pre {<br> background-color: #f5f5f5;<br> padding: 10px;<br> border-radius: 5px;<br> overflow-x: auto;<br> }<br> img {<br> max-width: 100%;<br> display: block;<br> margin: 20px auto;<br> }<br> code {<br> background-color: #f0f0f0;<br> padding: 2px 5px;<br> border-radius: 3px;<br> }<br>



Learning Vue Part 3: Building a Pomodoro Timer



Welcome back to our Vue.js learning journey! In this part, we'll dive into building a practical application: a Pomodoro timer. The Pomodoro Technique is a time management method using a timer to break down work into intervals, separated by short breaks. It's a popular technique for improving focus and productivity.



This tutorial will guide you through creating a basic Pomodoro timer using Vue.js. We'll cover essential concepts like:


  • Data binding and reactivity
  • Methods and event handling
  • Conditional rendering
  • Timers and intervals
  • Component structure


Let's get started!



Setting up the Project



If you haven't already, create a new Vue.js project using the Vue CLI:


vue create pomodoro-timer


Choose the default preset and install the necessary dependencies.



Creating the Timer Component



We'll create a new component named

Timer.vue

inside the

src/components

directory.



├── src
│ ├── components
│ │ └── Timer.vue
│ ├── main.js
│ └── App.vue
├── babel.config.js
├── public
│ └── index.html
├── package.json
└── vue.config.js


The contents of

Timer.vue

will be:


  <template>
   <div class="timer-container">
    <h1>
     Pomodoro Timer
    </h1>
    <div class="time-display">
     {{ formattedTime }}
    </div>
    <button @click="startTimer">
     Start
    </button>
    <button @click="pauseTimer">
     Pause
    </button>
    <button @click="resetTimer">
     Reset
    </button>
   </div>
  </template>
  <script>
   export default {
  name: 'Timer',
  data() {
    return {
      timeRemaining: 25 * 60, // 25 minutes in seconds
      timerInterval: null,
    };
  },
  computed: {
    formattedTime() {
      const minutes = Math.floor(this.timeRemaining / 60);
      const seconds = this.timeRemaining % 60;
      return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
    },
  },
  methods: {
    startTimer() {
      this.timerInterval = setInterval(() => {
        this.timeRemaining--;
        if (this.timeRemaining <= 0) {
          this.stopTimer();
          // Handle timer completion (e.g., switch to break)
        }
      }, 1000);
    },
    pauseTimer() {
      clearInterval(this.timerInterval);
      this.timerInterval = null;
    },
    resetTimer() {
      this.timeRemaining = 25 * 60;
      this.stopTimer();
    },
    stopTimer() {
      clearInterval(this.timerInterval);
      this.timerInterval = null;
    },
  },
};
  </script>
  <style scoped="">
   .timer-container {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.time-display {
  font-size: 3em;
  margin-bottom: 20px;
}
  </style>


Here's a breakdown of the code:



  1. Template:
    Defines the HTML structure of the timer component.

  2. Data:

    • timeRemaining
      : Stores the time remaining in seconds, initialized to 25 minutes.

    • timerInterval
      : Stores the interval ID for the timer.

  3. Computed:

    • formattedTime
      : Formats the
      timeRemaining
      into a human-readable format (minutes:seconds).

  4. Methods:

    • startTimer
      : Starts the timer by setting the interval to decrement
      timeRemaining
      every second.

    • pauseTimer
      : Pauses the timer by clearing the interval.

    • resetTimer
      : Resets the
      timeRemaining
      to its initial value and stops the timer.

    • stopTimer
      : Stops the timer (used by both pause and reset).

  5. Style (scoped):
    Defines basic CSS for the timer component.


Integrating the Timer into App.vue



Now, let's use the

Timer

component in the main

App.vue

file:


  <template>
   <div id="app">
    <timer>
    </timer>
   </div>
  </template>
  <script>
   import Timer from './components/Timer.vue';

export default {
  name: 'App',
  components: {
    Timer,
  },
};
  </script>


This imports the

Timer

component and registers it for use in the

App

component. Now, when you run the app with

npm run serve

, you'll see the basic Pomodoro timer.



Adding Break Functionality



Let's enhance the timer by adding a break functionality after each work session:


  <template>
   <div class="timer-container">
    <h1>
     Pomodoro Timer
    </h1>
    <div class="time-display">
     {{ formattedTime }}
    </div>
    <button @click="startTimer">
     Start
    </button>
    <button @click="pauseTimer">
     Pause
    </button>
    <button @click="resetTimer">
     Reset
    </button>
   </div>
  </template>
  <script>
   export default {
  // ... (rest of the component code)
  data() {
    return {
      timeRemaining: 25 * 60, // 25 minutes in seconds
      timerInterval: null,
      isWorkSession: true, // Flag to track work/break session
    };
  },
  methods: {
    // ... (other methods)
    startTimer() {
      // ... (timer start logic)
      if (this.timeRemaining <= 0) {
        this.stopTimer();
        this.isWorkSession = !this.isWorkSession; // Switch session type
        if (this.isWorkSession) {
          this.timeRemaining = 25 * 60; // Reset work time
        } else {
          this.timeRemaining = 5 * 60; // Set break time
        }
      }
    },
    // ... (other methods)
  },
};
  </script>


We've introduced a new data property,

isWorkSession

, to track whether we're in a work session or a break. When the timer reaches zero, it toggles this flag and resets the time based on the current session type.



Adding UI Enhancements



Let's improve the UI to provide better feedback to the user.



  1. Visualizing Session Type:
    We can display the current session type (Work or Break) next to the timer.

  2. Progress Bar:
    A progress bar can visually represent the time elapsed.

  1. Displaying Session Type

Modify the template in Timer.vue :

  <template>
   <div class="timer-container">
    <h1>
     Pomodoro Timer
    </h1>
    <div class="time-display">
     {{ formattedTime }}
     <span v-if="isWorkSession">
      (Work)
     </span>
     <span v-else="">
      (Break)
     </span>
    </div>
    <button @click="startTimer">
     Start
    </button>
    <button @click="pauseTimer">
     Pause
    </button>
    <button @click="resetTimer">
     Reset
    </button>
   </div>
  </template>


We use conditional rendering with

v-if

to display "Work" or "Break" based on

isWorkSession

.


  1. Adding a Progress Bar

We can use a simple div as a progress bar. We'll calculate its width based on the remaining time and the total time of the session.

  <template>
   <div class="timer-container">
    <h1>
     Pomodoro Timer
    </h1>
    <div :style="{ width: `${progressBarWidth}%` }" class="progress-bar">
    </div>
    <div class="time-display">
     {{ formattedTime }}
     <span v-if="isWorkSession">
      (Work)
     </span>
     <span v-else="">
      (Break)
     </span>
    </div>
    <button @click="startTimer">
     Start
    </button>
    <button @click="pauseTimer">
     Pause
    </button>
    <button @click="resetTimer">
     Reset
    </button>
   </div>
  </template>
  <script>
   export default {
  // ... (rest of the component code)
  computed: {
    // ... (other computed properties)
    progressBarWidth() {
      const sessionTime = this.isWorkSession ? 25 * 60 : 5 * 60; // Total session time
      return ((sessionTime - this.timeRemaining) / sessionTime) * 100;
    },
  },
  // ... (rest of the component code)
};
  </script>
  <style scoped="">
   .timer-container {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.time-display {
  font-size: 3em;
  margin-bottom: 20px;
}
.progress-bar {
  background-color: #ccc;
  height: 10px;
  width: 0%;
  margin-bottom: 10px;
  border-radius: 5px;
}
  </style>


We've added a new computed property

progressBarWidth

, which calculates the width of the progress bar. The width is calculated as a percentage of the time elapsed compared to the total session time. The

:style

binding in the template updates the width dynamically based on

progressBarWidth

.



Now, you have a basic Pomodoro timer with a progress bar and session type indicator. Feel free to add more features and customize the UI according to your preferences.



Adding Audio Notifications



To enhance the user experience, we can add audio notifications for the start, pause, and completion of the timer.



First, you need to include audio files for these events. You can find free sound effects online or create your own. For this example, let's assume you have the following audio files in the

public

directory:



  • start.mp3

  • pause.mp3

  • complete.mp3


Now, modify the

Timer.vue

component to play these audio files in the appropriate methods:


  <script>
   export default {
  // ... (rest of the component code)
  methods: {
    // ... (other methods)
    startTimer() {
      // ... (timer start logic)
      const audio = new Audio('/start.mp3');
      audio.play();
    },
    pauseTimer() {
      // ... (timer pause logic)
      const audio = new Audio('/pause.mp3');
      audio.play();
    },
    stopTimer() {
      // ... (timer stop logic)
      if (this.timeRemaining <= 0) {
        const audio = new Audio('/complete.mp3');
        audio.play();
      }
    },
    // ... (other methods)
  },
};
  </script>



In each relevant method, we create a new



Audio



object, load the appropriate audio file, and play it using the



play()



method. Make sure the audio paths are correct in the



Audio



constructor.





Now, your Pomodoro timer will play sounds to indicate the start, pause, and completion of the timer.






Conclusion





In this tutorial, we've learned how to build a basic Pomodoro timer using Vue.js. We covered core concepts like data binding, methods, computed properties, conditional rendering, and event handling. We also explored ways to enhance the UI and add audio notifications.





Remember, this is just a starting point. You can customize and extend this timer to your liking. You can add features like:



  • Settings for work/break durations
  • Multiple work sessions before a longer break
  • Persistence to save progress
  • Integration with other productivity tools




As you continue your Vue.js journey, you'll find that these fundamental concepts are applicable to a wide range of applications. Keep experimenting and have fun building with Vue!




. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .