15.8 Displaying a Countdown Timer
NN 4, IE 4
15.8.1 Problem
You want to have a running
countdown timer showing on the page.
15.8.2 Solution
Countdown timers can take many forms. Look to the combination of HTML
and scripts in Example 15-3 of the Discussion for
inspiration for your particular timer implementation. The example
application sets the turn of the new year in the
user's time zone as the "zero
hour" for the timer, and displays a constantly
updated display of the days, hours, minutes, and seconds until that
time.
An onload event handler in the page invokes the countDown(
) function by way of the
setInterval( ) method:
<body onload="setInterval('countDown( )', 1000)">
The display is updated approximately every second because the
setInterval( ) method repeatedly invokes the
countDown( ) function until a script cancels the
timer.
15.8.3 Discussion
The display of the timer can be in a text input field (all scriptable
browsers), swappable images (IE 4 or later and NN 3 or later), or
body text (IE 4 or later and NN 6 or later). This example uses
swappable images because it lends itself to the most flexible page
designs of the choices. But for other syntactic reasons, Navigator 4
is the minimum supported browser for this example. Figure 15-2 shows the output.
Example 15-3 shows the complete HTML document for
this application, including the HTML and scripts.
Example 15-3. A countdown timer application
<html>
<head>
<title>Countdown Timer</title>
<style type="text/css">
table {table-collapse:collapse; border-spacing:0}
td {border:2px groove black; padding:7px; background-color:#ccffcc}
th {border:2px groove black; padding:7px; background-color:#ffffcc}
.ctr {text-align:center}
</style>
<script type="text/javascript">
if (document.images) {
var imgArray = new Array( );
imgArray[0] = new Image(60,120);
imgArray[0].src="digits/0.gif";
imgArray[1] = new Image(60,120);
imgArray[1].src="digits/1.gif";
imgArray[2] = new Image(60,120);
imgArray[2].src="digits/2.gif";
imgArray[3] = new Image(60,120);
imgArray[3].src="digits/3.gif";
imgArray[4] = new Image(60,120);
imgArray[4].src="digits/4.gif";
imgArray[5] = new Image(60,120);
imgArray[5].src="digits/5.gif";
imgArray[6] = new Image(60,120);
imgArray[6].src="digits/6.gif";
imgArray[7] = new Image(60,120);
imgArray[7].src="digits/7.gif";
imgArray[8] = new Image(60,120);
imgArray[8].src="digits/8.gif";
imgArray[9] = new Image(60,120);
imgArray[9].src="digits/9.gif";
}
var nextYear = new Date( ).getYear( ) + 1;
nextYear += (nextYear < 1900) ? 1900 : 0;
var targetDate = new Date(nextYear,0,1);
var targetInMS = targetDate.getTime( );
var oneSec = 1000;
var oneMin = 60 * oneSec;
var oneHr = 60 * oneMin;
var oneDay = 24 * oneHr;
function countDown( ) {
var nowInMS = new Date( ).getTime( );
var diff = targetInMS - nowInMS;
var scratchPad = diff / oneDay;
var daysLeft = Math.floor(scratchPad);
// hours left
diff -= (daysLeft * oneDay);
scratchPad = diff / oneHr;
var hrsLeft = Math.floor(scratchPad);
// minutes left
diff -= (hrsLeft * oneHr);
scratchPad = diff / oneMin;
var minsLeft = Math.floor(scratchPad);
// seconds left
diff -= (minsLeft * oneMin);
scratchPad = diff / oneSec;
var secsLeft = Math.floor(scratchPad);
// now adjust images
setImages(daysLeft, hrsLeft, minsLeft, secsLeft);
}
function setImages(days, hrs, mins, secs) {
var i;
days = formatNum(days, 3);
for (i = 0; i < days.length; i++) {
document.images["days" + i].src = imgArray[parseInt(days.charAt(i))].src;
}
hrs = formatNum(hrs, 2);
for (i = 0; i < hrs.length; i++) {
document.images["hours" + i].src = imgArray[parseInt(hrs.charAt(i))].src;
}
mins = formatNum(mins, 2);
for (i = 0; i < mins.length; i++) {
document.images["minutes" + i].src = imgArray[parseInt(mins.charAt(i))].src;
}
secs = formatNum(secs, 2);
for (i = 0; i < secs.length; i++) {
document.images["seconds" + i].src = imgArray[parseInt(secs.charAt(i))].src;
}
}
function formatNum(num, len) {
var numStr = "" + num;
while (numStr.length < len) {
numStr = "0" + numStr;
}
return numStr
}
</script>
</head>
<body style="margin-left:10%; margin-right:10%"
<h1>New Year's Count-Down Timer</h1>
<hr />
<table cellspacing="5" cellpadding="5">
<tr>
<td align="right">
<img name="days0" src="digits/0.gif" height="120" width="60" alt="days">
<img name="days1" src="digits/0.gif" height="120" width="60" alt="days">
<img name="days2" src="digits/0.gif" height="120" width="60" alt="days">
</td>
<td align="left">
<img src="digits/days.gif" height="120" width="260" alt="days">
</td>
</tr>
<tr>
<td align="right">
<img name="hours0" src="digits/0.gif" height="120" width="60" alt="hours">
<img name="hours1" src="digits/0.gif" height="120" width="60" alt="hours">
</td>
<td align="left">
<img src="digits/hours.gif" height="120" width="360" alt="hours">
</td>
</tr>
<tr>
<td align="right">
<img name="minutes0" src="digits/0.gif" height="120" width="60" alt="minutes">
<img name="minutes1" src="digits/0.gif" height="120" width="60" alt="minutes">
</td>
<td align="left">
<img src="digits/minutes.gif" height="120" width="450" alt="minutes">
</td>
</tr>
<tr>
<td align="right">
<img name="seconds0" src="digits/0.gif" height="120" width="60" alt="seconds">
<img name="seconds1" src="digits/0.gif" height="120" width="60" alt="seconds">
</td>
<td align="left">
<img src="digits/seconds.gif" height="120" width="460" alt="seconds">
</td>
</tr>
</table>
</body>
</html>
For this example, the target date of the countdown timer is the start
of the next year in the user's local time zone. The
HTML output display is a rudimentary table with place holders for the
digit images. Because this application works with browsers that may
experience CSS formatting problems, we use the old-fashioned, but
still supported, align attribute in table cells
and name attribute in img
elements. The table is delivered to the browser with all of the
swappable images set to the zero digit representation.
The script portion of the page begins by precaching the images that
represents the numbers to prevent any delay the first time they are
needed. Ten images, each the same height and width, are loaded into
the browser's image cache while the page loads.
Several functions will execute repeatedly, and they benefit from the
one-time predefinition of constants as global variables. Because this
part of the code can run in all but first-generation browsers, it
includes a fix for a Y2K problem with the getYear(
) method of the Date
object (getFullYear( ) debuted in Version 4
browsers).
The function that executes repeatedly, countDown(
), performs the date math by comparing
the current clock setting (read approximately every second) against
the target date. Invoked by way of the window.setInterval(
) method, the countDown( ) function
executes repeatedly once every second (plus or minus system latency).
Next is the setImages(
) function, which adjusts the swappable
img element src properties.
This function must convert the numeric values passed from
countDown( ) into the URLs for the images. Calling
formatNum( ) for each countdown component returns
a string version of the number, including a leading zero where
necessary.
This application is a good demonstration of a situation where it
makes sense to use setInterval(
) to drive the repeated action. While
setTimeout( ) is intended for a single invocation
of a function after some delay, setInterval( )
automatically invokes a function repeatedly. If needed for older
browser support (pre-dating the availability of setInterval(
) in Version 4 browsers), you can simulate the behavior by
recursively invoking the countDown( ) function
from a setTimeout( ) method in the last statement
of the function:
setTimeout("countDown( )", 1000);
As with the timed autoscrolling of the page in Recipe 15.5, the
repeated calls to the countdown timer can also exhibit less than
smooth running. If you watch this code run for several seconds, you
will notice an occasional and random unevenness to the flipping of
the seconds. You can improve the smoothness by shortening the
interval delay (to 100, for example), but this means that the
function is being invoked 10 times per second, which could impact
other script processing in your page. You can experiment with
different settings to achieve the balance that feels best for your
application.
So far, we have focused on the user's local time
zone, but there may be other cases in which you want to peg the
target date and time to a unique event occurring someplace on the
planet, such as a corporate press conference announcing a new product
release. To keep the timing accurate, you need to perform some
additional calculations to get the time zone issues under control, so
that users from Australia to Greenland will see the exact same
countdown times leading up to the singular event.
The coding for this is simple, but the timekeeping may not be. To peg
the target time and date to a universal point in the future, change
the global variable declarations shown in Example 15-3 from this:
var targetDate = new Date(nextYear,0,1);
var targetInMS = targetDate.getTime( );
to this:
var targetInMS = Date.UTC(nextYear, 0, 1, 0, 0, 0);
This is for the stroke of midnight on New Year's Eve
at Greenwich Mean Time (GMT). If you have
another date and time, simply plug the values into the six parameters
in the order of four-digit year, month (zero-based), date, hour,
minute, and second, but at Greenwich Mean Time. For example, if you
plan to offer a web cast of a recital at 8 P.M. in Los Angeles on
March 10, 2003, you need to determine what that time is in GMT so you
can provide a countdown timer on the announcement page. Los Angeles
is in the Pacific Time Zone, and in that part of the year, the zone
is Pacific Standard Time. The PST zone is 8 hours earlier than GMT.
When it's 8 P.M. in Los Angeles,
it's 4 A.M. the next day at GMT. Thus,
you'd set the target time for 4 A.M. on the 11th of
March (month index is 2) like this:
var targetInMS = Date.UTC(2003, 2, 11, 4, 0, 0);
There is an even easier way to figure out this GMT stuff. Set your
system clock and time zone to the local time of where the event is to
occur (you may already be there). Then run this little calculator
page to determine the GMT date and time:
<html>
<head>
<script type="text/javascript">
function calcGMT(form) {
var date = new Date(form.year.value, form.month.value, form.day.value,
form.hour.value, form.minute.value, form.second.value);
form.output.value = date.toUTCString( );
}
</script>
</head>
<body>
<form>
Year:<input type="text" name="year" value="0000"><br>
Month (0-11):<input type="text" name="month" value="0"><br>
Day (1-31):<input type="text" name="day" value="0"><br>
Hour (0-23):<input type="text" name="hour" value="0"><br>
Minute (0-59):<input type="text" name="minute" value="0"><br>
Second (0-59):<input type="text" name="second" value="0"><br>
<input type="button" value="Get GMT" onclick="calcGMT(this.form)"><br>
<input type="text" name="output" size="60">
</body>
</html>
If you change your system clock settings to use this calculator, be
sure to change it back to your local time and time zone.
Another kind of counter is a short-term counter for applications like
student quizzes or other controlled environments in which you need
something to time out or navigate to another page.
It's not uncommon in such cases to display, say, a
60-second counter.
The following code is a modified version of the script in Example 15-3 that displays a countdown timer for the number of
seconds passed as a parameter to the primary function:
// global variables
var targetInMS, timerInterval;
var oneSec = 1000;
// pass the number of seconds to count down
function startTimer(secs) {
var targetTime = new Date( );
targetTime.setSeconds(targetTime.getSeconds( ) + secs);
targetInMS = targetTime.getTime( );
// display starting image
setImages(secs);
timerInterval = setInterval("countDown( )", 100);
}
function countDown( ) {
var nowInMS = (new Date( )).getTime( );
var diff = targetInMS - nowInMS;
if (diff <= 0) {
clearInterval(timerInterval);
alert("Time is up!");
// more processing here
} else {
var scratchPad = diff / oneSec;
var secsLeft = Math.floor(scratchPad);
setImages(secsLeft);
}
}
function setImages(secs) {
var i;
secs = formatNum(secs, 2);
for (i = 0; i < secs.length; i++) {
document.images["seconds" + i].src = imgArray[parseInt(secs.charAt(i))].src;
}
}
function formatNum(num, len) {
var numStr = "" + num;
while (numStr.length < len) {
numStr = "0" + numStr;
}
return numStr
}
This code is easily modifiable to extend the timer to minutes and
seconds if you like.
15.8.4 See Also
Recipe 15.7 for displaying the number of days until Christmas;
Recipe 2.9 and Recipe 2.10 for the Date object and its
methods.
|