I've been following the challenges on Scotch.io for awhile and saw one that I was really interesting in trying to make. It was a Pomodoro timer for the Scotch.io Challenge #6. Always looking to test my skills I wanted to take a crack at this one.
Setup
The setup was easy as there already was a codepen (below) with all the required html and css work done. With the major work ahead of me it was time to start working on the javascript portion of this challenge.
This codepen doesn't work
First Steps
The first thing I wanted to do was setup my data with all the variables I would need.
data: {
message: 'Let the countdown begin!!',
timerRunning: false
}
This just created the variable for my messaging, which would change depending on what state the timer is in, and the states to differentiate from the timer being active or being paused. These states would be crucial to creating my methods in relation to getting the timer to countdown.
The methods came pretty naturally in reference to their connections with the buttons. I needed to attach a method to each button that would run on click. The requirement called for 4 buttons (Start, Pause, Resume, and Reset).
The start button would turn on the countdown and make the timerRunning: true
, since the timer would be running. The pause button would freeze the countdown and make the timerRunning: false
. The resume button would turn the countdown back on at it's current time and pace while making timerRunning: true
. Finally the reset button would set the countdown to it's starting number and make timerRunning: false
.
This is the original code for the methods relating to the functionality we just talked about. Including the changing of the message on the certain states.
methods: {
timerRun() {
this.timerRunning = true;
this.message = 'Greatness is within sight!!!';
},
timerPause() {
this.message = 'Never quit, keep going!!';
this.timerRunning = false;
},
timerReset() {
this.message = 'Let the countdown begin!!';
this.timerRunning = false;
},
timerCountdown() {
this.timerRunning = true;
}
}
To change the message for certain steps I tied the methods, shown above, to the buttons, shown below, and this triggers different actions. Depending on what button was pressed it could say the timer is running, the timer is paused, the timer was reset, or the timer is running. With the variable of timerRunning
changing in the scenarios that also would change which button configuration was being shown at the moment, with the v-if function. So, this took care of the functions of the buttons and it's time to actually get the timer working.
<div class="buttons">
<button @click="timerRun" v-if="!timerRunning">Start</button>
<button @click="timerPause" v-if="timerRunning">Pause</button>
<button @click="timerReset" v-if="timerRunning">Restart</button>
</div>
When I went to create the timer I realized I didn't really know how to code something counting down and didn't understand the basic principles to create a timer. To learn how this should work I took a quick deviation into making a clock.
I learned about using the milliseconds to base all the clock actions on, how to step through time, and display hours, minutes, seconds and milliseconds. From this side project I learned a lot about time management when it comes to going forward or backward through time.
A major problem that I was having for the countdown timer was consistently moving through time. When I first created it, whenever the start/resume button was pressed after the initial start, the countdown would speed up incrementally for every time pressed. This was not the expected result and not conducive to something where you'd need to resume. After making this clock I realized a more consistent method of triggering the start of the timer.
data {
interval: null
},
methods: {
timerRun() {
this.timerRunning = true;
this.message = 'Greatness is within sight!!!';
this.interval = setInterval(this.countdownTimer, 1000);
}
timerPause() {
this.message = 'Never quit, keep going!!';
this.timerRunning = false;
clearInterval(this.interval);
},
timerReset() {
this.message = 'Let the countdown begin!!';
this.timerRunning = false;
clearInterval( () => { this.interval; });
}
}
This code was important to having a consistent movement in the countdown from initial run to any subsequent resumes. Now when the timer is started a new this.interval
is started to countdown the timer. On a pause and reset that variable is cleared, which pauses the countdown and stops the variable from multiplying on top of each other.
Getting the timer to countdown was a long road of understanding a lot of math, which I'm sadly very poor at. In the end I needed to break down the interpretation of time into — hours are 60*60*60, minutes are 60*60 and milliseconds are 60. So you need to take the milliseconds and times up. (I apologize if I'm explaining this poorly, I'm horrible at math).
Now the other problem with counting down, how not to go into negative numbers. With the explanation below this is the reason that the time doesn't become negative (it actually does but we don't show that).
timerCountdown() {
console.log('Working');
this.timerRunning = true;
this.interval = setInterval(this.updateCurrentTime, 1000);
// Counts down from 60 seconds times 1000.
setInterval( () => {
this.timerMinutes--
}, 60 * 1000)
// Check if seconds at double zero and then make it a 59 to countdown from.
// need another method of checking the number while in the loop and then adding a zero on the number under 10
if(this.timerSeconds === '00'){
this.timerSeconds = 59;
setInterval( () => {
this.timerSeconds--
}, 1000);
} else {
setInterval( () => {
this.timerSeconds--
}, 1000);
}
},
It does go into negatives in his solution. You could include a simple check if time <= 0 to reset and stop the timer. And how it stays in 60 seconds is just maths. He converts the rounded minutes into seconds and subtracts them from the total time (in seconds). So what will be left are seconds between 0 and 60.
This could be shortened and cleared up using modulo.
this.totalTime % 60
This will always leave the remainder of a number 0 - 60.
Thanks to Zammy13 for answering my question
The breakdown of the modulo (Remainder %).
For my timer I wanted 25 minutes, so I used this instead totalTime: (25 * 60)
. This equals the total amount of time (25 minutes) times 60 which equals the amount in seconds. The totalTime is then 1500 in seconds.
computed: {
time: function() {
return this.minutes + " : " + this.seconds;
},
hours: function() {
var milli = this.milliseconds;
// var hrs = new Date().getHours();
// Used getHours() since the below didn't work for me
var hrs = Math.floor((milli / 3600000) % 24);
if (hrs >= 13) { hrs = hrs - 12 }
return hrs >= 10 ? hrs : '0' + hrs;
},
minutes: function() {
var min = Math.floor(this.totalTime / 60);
return min >= 10 ? min : '0' + min;
},
seconds: function() {
var sec = this.totalTime - (this.minutes * 60);
return sec >= 10 ? sec : '0' + sec;
}
}
The final step was to make sure that your timer knew it was counting down. Which is probably the easiest part of this whole thing, just checking that the variable timerRunning == true
and then removing a millisecond.
countdownTimer() {
if (this.timerRunning == true) {
this.totalTime--;
}
}
End
It was a long road and a lot more work than I thought it was going to be. In the end I made something that is basic and can't wait to make something with all the bells and whistles. Something to tell you where you are in the pomodoro technique and something to make it visually more fun.
The final codePen for the challenge
This codepen has a lot of problems, including an accelerated countdown. I fixed this in my personal version of the timer. This was done to reach a goal and that was the challenge due date. There will be a part two of my own advanced timer.
Top comments (21)
Hey Tori. Great post! I loved the way you described the steps for achieving the final implementation, you explained it in a clear and simple way :)
Just a tiny detail that I noticed in your code was that you are not really clearing the interval when you click the reset button. That results in a weird interval between the seconds. Try to click the reset button then start the countdown again.
One way to solve that is to in fact clear the interval within the
timerReset
function.Instead of
clearInterval( () => { this.interval; });
you should have
clearInterval(this.interval);
What do you think?
Hmm... I'll look into it because that's what I wanted to do (what you said). So if it's not doing that I'm unsure. Otherwise it get incrementally faster and that's now what we want at all. Thanks for pointing it out.
Thanks for the post Tori! After seeing you talk at CodeLand I feel like I'm reading it in your presentation style. Very clearly describe 👌
Thanks, I'm not sure if that is a good thing or a bad thing. I need to work on relaxing more when I present. 😌 But I want to be as descriptive as possible so I'm glad that came through.
Good thing! I thought your presentation I saw was clear and memorable. Everybody has a bit they need to work on but I really loved seeing you talk.
Great post, thanks for sharing.
There's a lot of interesting parts to something that sounds very simple.
Hi, I clicked on play it doesn't countdown ?
Did you click on the first codepen at the top? That one is just a layout they made so you could start on the coding mechanics. It does not work, the working one is at the very bottom.
OK thanks, I used first one :)
By the way was interested by your pomodoro as I also made one in red ;) myminiapps.space/countdown/
Hey, there is an issue with your timer. If you quickly click on start, reset, start reset about 5 or six times then the last time the timer will go nuts and almost countdown at triple speed
Interesting, odd usage (wildly clicking buttons) but I'm game to figuring this out. Sounds like maybe a reset button problem.
Finding use and abuse cases are part of the game
Love this, been needing a good linux timerfor countdown and countup. Was in the same place lol. How the heck do I start. This is exactly what I needed. Thanks!
No problem. I didn't understand how complex a timer/countdown could be.
This don't work in main codepen link
You mean the first pen?
My bad... sorry it's work fine, you did this template to begin the tutorial
Great post, Tori & great break down of your project. We did this in class this week and it’s empowering to read & understanding what you did in your code. Great job all the way around!
No tests?
Never done them. I'm self taught and that's a section I haven't gotten to yet. I feel it would be easy to be taught later. If you've got some insight do let me know, I'm quite curious.