Why this is important to me
I have a site where I need to have a countdown timer running to show people how much time they have left to complete an action.
This timer will run for days and probably just use MomentJS to say something like "in 4 days" from MomentJS's to()
function.
However when we have an hour left to go I'm going to switch over to a countdown by minutes timer, eventually when the minutes get low enough, I'm going to have a seconds timer involved. When we're getting down to the very last few minutes I'm going to even display milliseconds.
The problem
Pretty much there are two main techniques to animate the countdown timer.
setInterval()
requestAnimationFrame()
Well, right away I noticed that the requestAnimationFrame()
method was much more smooth to the eye, it works great - especially when I'm displaying milliseconds. However it wasn't long when I noticed that my poor computer started to get a little warm. All of this "animation" is causing quite a load on my CPU. I tried to use CPU monitors, and looked around at ways to see how much load this puts on my CPU but, overall I can't really find a tool that gives me a clear graph of what kind of CPU load my little countdown timer is using.
So, I decided to find a way to limit the FPS and see if that would reduce my problem. And yes, it does. If you use setTimeout()
in tandem with requestAnimationFrame()
you can set a waiting time before you call your next function.
Which raises the question, if you're using setTimeout()
- Why don't you just use setInterval()
and forget about the extra optimization that requestAnimationFrame()
gives you?
I did some looking around and found another method which simply checks to see if the right amount of interval time has passed since the last time requestAnimationFrame()
called your function. I made some optimizations to how this code works and ended up with one of the two functions I'm trying to measure below.
In the end, I'd really like to have a more clear way to measure this - because the activity monitor on my mac is hardly reliable tool to give an accurate reading - and I can't find a way to measure just the code I'm running.
Chrome has some more tools, the profiler, and the timeline - which are both very helpful, but they don't give me the metric I'm looking for - CPU load.
The Code:
Here are four code snippets, which do exactly the same thing - all of them use:
- MomentJS
- CountdownJS
- jQuery
The code is 100% identical, the only difference is how I am limiting the FPS of the animation.
I'd like to find a way to measure (as precisely as possible) the difference between the four functions in how much CPU load they are taking. And then I'd like to then change the FPS around to see if I can find an acceptable load for my application and then I can find the sweet spot - the right amount of FPS, during the different timer stages.
Technique 1 - setTimeout()
var now = moment(); // new Date().getTime();
var then = moment().add(60, 'seconds'); // new Date(now + 60 * 1000);
$(".now").text(moment(now).format('h:mm:ss a'));
$(".then").text(moment(then).format('h:mm:ss a'));
$(".duration").text(moment(now).to(then));
(function timerLoop() {
setTimeout(function(){
$(".difference").text(moment().to(then));
$(".countdown").text(countdown(then, null, countdown.YEARS | countdown.MONTHS | countdown.DAYS | countdown.HOURS | countdown.MINUTES | countdown.SECONDS | countdown.MILLISECONDS).toString());
requestAnimationFrame(timerLoop);
}, 1000/30);
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js" type="text/javascript"></script>
<script src="https://cdn.rawgit.com/mckamey/countdownjs/master/countdown.min.js" type="text/javascript"></script>
<script src="https://code.jquery.com/jquery-3.0.0.min.js" type="text/javascript"></script>
<div>
The time is now: <span class="now"></span>, a timer will go off <span class="duration"></span> at <span class="then"></span>
</div>
<div>The timer is set to go off <span class="difference"></span></div>
<div class="countdown"></div>
Technique 2 - Delta between intervals
var now = moment(); // new Date().getTime();
var then = moment().add(60, 'seconds'); // new Date(now + 60 * 1000);
$(".now").text(moment(now).format('h:mm:ss a'));
$(".then").text(moment(then).format('h:mm:ss a'));
$(".duration").text(moment(now).to(then));
var fps = 30;
var interval = 1000/fps;
var performanceTime = performance.now();
var performanceDelta;
(function updateCountdown(time) {
performanceDelta = time - performanceTime;
if (performanceDelta > interval) {
performanceTime = time - (performanceDelta % interval);
$(".difference").text(moment().to(then));
$(".countdown").text(countdown(then, null, countdown.YEARS | countdown.MONTHS | countdown.DAYS | countdown.HOURS | countdown.MINUTES | countdown.SECONDS | countdown.MILLISECONDS).toString());
}
requestAnimationFrame(updateCountdown);
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js" type="text/javascript"></script>
<script src="https://cdn.rawgit.com/mckamey/countdownjs/master/countdown.min.js" type="text/javascript"></script>
<script src="https://code.jquery.com/jquery-3.0.0.min.js" type="text/javascript"></script>
<div>
The time is now: <span class="now"></span>, a timer will go off <span class="duration"></span> at <span class="then"></span>
</div>
<div>The timer is set to go off <span class="difference"></span></div>
<div class="countdown"></div>
Technique 3 - setInterval()
var now = moment(); // new Date().getTime();
var then = moment().add(60, 'seconds'); // new Date(now + 60 * 1000);
$(".now").text(moment(now).format('h:mm:ss a'));
$(".then").text(moment(then).format('h:mm:ss a'));
$(".duration").text(moment(now).to(then));
var fps = 30;
var interval = 1000/fps;
setInterval(function updateCountdown() {
$(".difference").text(moment().to(then));
$(".countdown").text(countdown(then, null, countdown.YEARS | countdown.MONTHS | countdown.DAYS | countdown.HOURS | countdown.MINUTES | countdown.SECONDS | countdown.MILLISECONDS).toString());
}, interval);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js" type="text/javascript"></script>
<script src="https://cdn.rawgit.com/mckamey/countdownjs/master/countdown.min.js" type="text/javascript"></script>
<script src="https://code.jquery.com/jquery-3.0.0.min.js" type="text/javascript"></script>
<div>
The time is now: <span class="now"></span>, a timer will go off <span class="duration"></span> at <span class="then"></span>
</div>
<div>The timer is set to go off <span class="difference"></span></div>
<div class="countdown"></div>
It would also be interesting to see a completely un-throttled version like so:
Technique 4 - No throttle
var now = moment(); // new Date().getTime();
var then = moment().add(60, 'seconds'); // new Date(now + 60 * 1000);
$(".now").text(moment(now).format('h:mm:ss a'));
$(".then").text(moment(then).format('h:mm:ss a'));
$(".duration").text(moment(now).to(then));
(function timerLoop() {
$(".difference").text(moment().to(then));
$(".countdown").text(countdown(then, null, countdown.YEARS | countdown.MONTHS | countdown.DAYS | countdown.HOURS | countdown.MINUTES | countdown.SECONDS | countdown.MILLISECONDS).toString());
requestAnimationFrame(timerLoop);
})();
// CountdownJS: http://countdownjs.org/
// MomentJS: http://momentjs.com/
// jQuery: https://jquery.com/
// Rawgit: http://rawgit.com/
// Light reading about the requestAnimationFrame pattern:
// http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
// https://css-tricks.com/using-requestanimationframe/
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js" type="text/javascript"></script>
<script src="https://cdn.rawgit.com/mckamey/countdownjs/master/countdown.min.js" type="text/javascript"></script>
<script src="https://code.jquery.com/jquery-3.0.0.min.js" type="text/javascript"></script>
<div>
The time is now: <span class="now"></span>, a timer will go off <span class="duration"></span> at <span class="then"></span>
</div>
<div>The timer is set to go off <span class="difference"></span></div>
<div class="countdown"></div>
Simply Put: How does one measure the CPU load difference between four similar javascript functions?
Does anyone already know which of these is going to be more performant? (I know that is not really a word)