[Labyrinthe Noir]>[Top]>[こども工作教室]>

「ストップウォッチ」のソースと解説

ストップウォッチの説明

時間を計るための道具がストップウォッチです。

スタート/ストップのボタンを使って、計測を開始したり、止めたりします。通常はこれを1つのボタンで行います。
ラップタイムを計る時計もありますが、今回は仕組みを簡単にしたいので、この機能は省きます。ラップタイムは、途中経過の時間表示をしながらも時計は計測を続けるという機能です。

保存ファイルの準備

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>ストップウォッチ</title> </head> <body>
<h3>ストップウォッチ</h3> <hr> </body> </html>

エディタ(メモ帳)とブラウザ(Internet Explorer)を使って基本のタグを書いたHTMLファイルを作成します。
まずはメモ帳を開き、左記のタグを記入して、デスクトップに「名前を付けて保存」します。ファイル名は「sw.html」です。
保存が出来たら、アイコンがデスクトップに出てくるので、ダブルクリックして開きます。
画面の左右にメモ帳とブラウザそれぞれのウインドウを置いて作業しましょう。
メモ帳を閉じてしまった場合は、アイコンの上で右クリックしてショートカットメニューを表示させます。「プログラムから開く」の中から「NotePad」を選ぶと、メモ帳でファイルを開くことができます。

フォーム

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>ストップウォッチ</title> </head> <body> <h3>ストップウォッチ</h3> <hr> <form name="form_sw"> <input name="counter" type="text"> <input name="bstart" type="button"value="スタート"> <input name="breset" type="button"value="リセット"> </form> </body> </html>

フォームには時間の表示覧と2つのボタンを用意します。
1つ目のボタンが、計測を開始する「スタート」ボタンです。停止のボタンにも使います。
2つ目のボタンは、計測した時間を消すための「リセット」ボタンです。

初期設定

スクリプトを書き始めましょう。まずは、初期設定用のユーザー関数です。

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>ストップウォッチ</title> </head> <body> <h3>ストップウォッチ</h3> <hr> <form name="form_sw"> <input name="counter" type="text" value="00:00:00"> <input name="bstart" type="button"value="スタート"> <input name="breset" type="button"value="リセット"> </form> <script type="text/javascript"> reset_form(); function reset_form() {
timer = 0;
document.form_sw.counter.value = 0;
}
</script>

</body>
</html>

ユーザー関数reset_form()を準備します。
スクリプトの最初に初期設定として呼びだし、後でリセットボタンを押したときにも使います。

ユーザー関数内では、変数timerで時間を管理するため、まず「0」に設定します。
続けてフォームにも「0」を出力します。

ユーザー関数とは「function」で始まる部分のことです。
ここでは「reset_form」という名前のユーザー関数を作成しました。
これは、スクリプトやボタンの操作によって、何度も呼び出したり、繰り返し利用するプログラムのかたまりです。
プログラムやスクリプトの基本は上から順番に命令を実行して行くことです。
しかし、中には同じ事を繰り返したり、途中で条件に応じて複数の答えを用意しなければいけないことがあります。
そんなときのために、同じ事を何度も書かなくて良いように、1つのかたまりにして名前を付けておきます。
そうすることで、スクリプトの途中から呼び出して処理をしたり、ボタンや答えに合わせて異なる処理を選ぶことができるのです。

フォームからの呼び出し

フォームのボタンを押すと、ユーザー関数を呼び出すように設定しておきましょう。

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>ストップウォッチ</title> </head> <body> <h3>ストップウォッチ</h3> <hr> <form name="form_sw"> <input name="counter" type="text" value="00:00:00"> <input name="bstart" type="button"value="スタート" onClick="start_count()"> <input name="breset" type="button"value="リセット" onClick="reset_form()"> </form> <script type="text/javascript"> reset_form(); function reset_form() {
timer = 0; document.form_sw.counter.value = 0; }
</script>
</body>
</html>

「onClick」というイベント処理を利用します。これはボタンが「クリックされたとき」に処理しなさいという意味です。
「onClick」の前に半角スペースを入れるのを忘れないでください。

リセットボタンには、先ほど作ったユーザー関数reset_formの呼び出しを記述します。最後には必ず「()」を付けます。これでユーザー関数だと判ります。

スタートボタンには、次に作るユーザー関数start_countの呼び出しを記述します。

時間を計測する

ユーザー関数start_countのスクリプトを作っていきましょう。
まずは、時間の計測を進める仕組みを作ります。

<script type="text/javascript"> 
reset_form();

function reset_form() {
	timer = 0; 
	document.form_sw.counter.value = 0;
}
 					
function start_count() { 					
	timerID = setInterval("count_up()",1000); 
}

function count_up() {
	timer++;
	document.form_sw.counter.value = timer;
}
</script>

時間を計測には、タイマーという機能を使います。タイマーの呼び出しと、呼び出されて繰り返す処理の2つが必要になります。
ここではユーザー関数start_countがタイマーを呼び出すスクリプトです。
変数timerIDを用意して、setInterval関数を使って一定間隔で次のユーザー関数を呼び出します。1/1000秒単位で呼び出し間隔を記述しますので、ここでは「1000」=1秒になります。

【setInterval関数】
引数には、実行命令と、命令の繰り返し間隔を指定します。
実行命令には、ユーザー関数を指定することができます。
繰り返し間隔は、1/1000秒単位です。
このスクリプトの場合、1秒毎にユーザー関数count_upを呼び出して実行します。

次に呼び出されるユーザー関数count_upです。
ここが時計の心臓部にあたります。
1行目で変数timerに1を足しています。
2行目は、変数timerの値をフォームに出力しています。

「timer++」という書き方は、省略した代入式の書き方です。
正しくは「timer = timer + 1」と書くのですが、上のように省略して良いことになっています。
同様に「timer = timer - 1」の場合「timer--」と書きます。

【この時点の sw.html を別枠で表示】

まだ、時計が進む機能しかありませんので、スタートボタンを押して時計が動くことだけ確認してください。
リセットボタンも一瞬動作はしますが、タイマーは止まりません。
スタートボタンを押す毎に、別のタイマーが起動するため、動作がおかしくなります。

時間を止める

時計を止めるために、まず準備をしましょう。

<script type="text/javascript"> 
sw_status = 0;
reset_form();

function reset_form() {
	timer = 0; 
	document.form_sw.counter.value = 0;
}
 				
function start_count() { 
	if (sw_status == 0) { 				
		sw_status = 1;
		timerID = setInterval("count_up()",1000); 
	} else {
		sw_status = 0;
		clearInterval(timerID);
	}
}
 				
function count_up() {
	timer++;
	document.form_sw.counter.value = timer;
}
</script>

時計が動いている時と止まっているときを判断するために、その状態を記録する必要があります。
変数sw_statusを用意して、これが「0」ならば停止、「1」ならば動作中を表すことにします。

この「sw_status = 0;」の記述ですが、初期設定としてスクリプトの最初に書いています。
しかし、本来ならばユーザー関数reset_formの中に書く方が良いと思われます。リセットしたときに、これも初期状態に戻った方が良いからです。
でも、あえてこうしているのは、別の方法があるからなのです。

次に、この変数sw_statusを使って、分岐を作ります。
if文とelseを使って、停止と動作中の流れを分けます。
「0」の時は、止まっているので、動作させなければなりません。なので、if文が真のときに、先ほど作ったタイマーの呼び出しを行います。
また、それと同時に変数sw_statusを「1」にして、動作中を表します。
elseの後は、if文が偽の場合なので、こちらは変数sw_statusを「0」に戻すようにしてやります。
そして、clearInterval関数でタイマーを停止しています。このとき、変数timerIDを指定しているので、動作中のタイマーが止まることができます。

変数timerIDには、タイマーを動作させたときに識別番号が入ります。これによって、1つ目のタイマー、2つ目のタイマーと分けて動かしたり、止めたりすることができます。
また、このスクリプトでは、タイマーの動作と停止が交互に行われるため、2つのタイマーが同時に動くことはありません。

フォームを判りやすく改良

時計が動作していることは、表示の時間が増えていることで判ります。
しかし、これだけでは不親切だと思いますので、フォームのボタンの表示を切り換えたいと思います。

<script type="text/javascript"> 
sw_status = 0;
reset_form();

function reset_form() {
	timer = 0; 
	document.form_sw.counter.value = 0;
}
 				
function start_count() { 
	if (sw_status == 0) { 
		document.form_sw.bstart.value = "ストップ";
		sw_status = 1;
		timerID = setInterval("count_up()",1000); 
	} else {
		document.form_sw.bstart.value = "スタート";
		sw_status = 0;
		clearInterval(timerID);
	}
}
 				
function count_up() {
	timer++;
	document.form_sw.counter.value = timer;
}
</script>

スタートボタンを押したときの分岐の中で、変数sw_statusに対応した表示に切り換えます。
動作中は「ストップ」と、停止中は「スタート」と表示を切り換えます。

リセットボタンの機能を改善

ここでリセットボタンの機能を見直しましょう。
「スタート」「ストップ」「リセット」の順番にボタンを押すと問題なく動作しています。
しかし、「スタート」の後、「ストップ」を押さずに「リセット」を押すと、時計は止まりません。
そこで、動作中に「リセット」を押しても「ストップ」ボタンを押したことにすれば、正しく初期化が行えます。

<script type="text/javascript"> 
sw_status = 0;
reset_form();

function reset_form() {
	if (sw_status == 1) {start_count();}
timer = 0; 
	document.form_sw.counter.value = 0;
}
 				
function start_count() { 
	if (sw_status == 0) { 
		document.form_sw.bstart.value = "ストップ";
		sw_status = 1;
		timerID = setInterval("count_up()",1000); 
	} else {
		document.form_sw.bstart.value = "スタート";
		sw_status = 0;
		clearInterval(timerID);
	}
}
 				
function count_up() {
	timer++;
	document.form_sw.counter.value = timer;
}
</script>

ユーザー関数reset_formに1行付け加えます。
if文で変数sw_statusが「1」の場合に、ユーザー関数start_countを呼び出しています。これで、「ストップ」を押したことになります。

変数sw_statusの初期化は、「ストップ」を押したときの動作に含まれているため、ユーザー関数reset_formになくても良かったのです。

時刻表示の改良

時刻の表示は、1秒単位で変数timerの中身をそのまま表示しています。
しかし、これでは1分や1時間という単位になると、いつまでも秒単位では判りにくいと思います。
そこで、表示を「00:00:00」という形式に整えるようにしたいと思います。

<script type="text/javascript"> 
sw_status = 0;
reset_form();

function reset_form() {
	if (sw_status == 1) {start_count();}
	timer = 0; 
	document.form_sw.counter.value = count_format(0);
}
 				
function start_count() { 
	if (sw_status == 0) { 
		document.form_sw.bstart.value = "ストップ";
		sw_status = 1;
		timerID = setInterval("count_up()",1000); 
	} else {
		document.form_sw.bstart.value = "スタート";
		sw_status = 0;
		clearInterval(timerID);
	}
}
 				
function count_up() {
	timer++;
	document.form_sw.counter.value = count_format(timer);
}
 				
function count_format(num) {
	var ts = num % 60;
	var tm = Math.floor(num / 60);
	var th = Math.floor(tm / 60);
	tm = tm % 60; 
	return th + ":" + tm + ":" + ts;
}
</script>

ユーザー関数count_formatを作って、そこに数値を受け渡すと、整えられた形式で値(文字列)が戻ってくるようにします。

ユーザー関数の呼び出しは2ヶ所あります。
リセット時の「0」表示と、カウント時の表示です。それぞれ、出力していた値や変数をユーザー関数に引き渡すように()内に入れます。

ユーザー関数count_formatでも、引き渡された値を受け取るために変数numを用意します。
これはfunctionの行にある関数名の後ろの()内に、変数名を書くことで受け取りを行います。

では、ユーザー関数count_formatの中を見ていきます。
1行目では、変数numを60で割って、その余りを変数tsに入れています。60で割った余りなので、1分よりも小さな秒数が変数tsに入るのです。
次に、変数tmには、変数numを60で割ったときの、整数値が入ります。1行目と2行目で、秒数を分と秒に分けているのです。60で割った答えが分、余りが秒ですね。
3行目は、変数thに先ほどの変数tmを60で割った整数値が入ります。今度は分単位の中から60分以上の部分を取りだしています。
このままでは変数tmには、変数thで取り出した分の時間が入っていますので、4行目で、変数tmを60で割った余りを、もう一度変数tmに入れ直しています。
最後に、returnで値を返します。3つの変数を順番につなぎ「:」を間に挟んでいます。返された値は呼び出した位置のユーザー変数と置き換わって処理されます。

変数ts、変数tm、変数thは、最初に代入先になっているときに頭に「var」という文字が付いています。
これは変数についての特徴を表していて、ローカル変数であることを表しています。これをローカル宣言とも言います。
ユーザー関数内でローカル宣言を行った変数は、そのユーザー関数内でのみ有効な変数となります。
そのため、同じ名前の変数が、メインスクリプトや他のユーザー関数内にあっても、別のものとして扱われます。
同じ変数名を繰り返し使っていても、混同させないようにすることができるのです。

2行目と3行目の式には、「Math.floor()」という関数が使われています。
この「Math」は演算用の関数の頭に必ず必要なものです。次のfloorが実際の関数となっていて、小数点以下を切り捨てる命令になっています。
そのため、割り算をした後に整数だけが残るのです。

最後の式は「+」を使っていますが足し算ではありません。JavaScriptでは文字列をつなぐのにも「+」が使われるのです。他のプログラム言語では「&」を使うことがあります。
また、JavaScriptの特徴として、数値と文字列の変数を混在させることができます。式の中に文字列が混じっていると、そこから全体を文字列だと判断してくれるのです。

JavaScriptでは、変数の型宣言をせずに使うため、同じ変数に数値や文字列を入れることができます。
また、今回の式のように、数値と文字列をそのままつないでしまうこともできるのです。
しかし、数値の入った変数を先に「+」すると、これは数値として扱われてしまうため、そこでは足し算が発生するというややこしいことが起こります。
例:「1 + ":" + 2」 →「1:2」
例:「1 + 2 + ":"」→「3:」
例:「":" + 1 + 2」→「:12」 (先に+1が文字列化しているので、+2も同様)
例:「":" + (1 + 2)」→「:3」(()内を先に計算するため)

【この時点の sw.html を別枠で表示】

時刻表示の桁を揃える

ユーザー関数count_formatに次のように書き加えます。

function count_format(num) {
	var ts = num % 60;
	var tm = Math.floor(num / 60);
	var th = Math.floor(tm / 60);
	tm = tm % 60; 
	if (ts < 10) ts = "0" + ts;
	if (tm < 10) tm = "0" + tm;
	if (th < 10) th = "0" + th;
	return th + ":" + tm + ":" + ts;
}

これでうまく行ったかと思ったら、実際に動かしてみるとあることに気が付きます。
それは「00:00:00」ではなく、「0:0:0」と表示されてしまうという問題です。
0~9までの一桁の数字の場合、「00」や「09」と表示されるのが時計のデジタル表示です。
そこで修正を加えることにしましょう。

どれも同じ処理を行うのでユーザー関数にして呼び出しても良いのですが、繰り返しても3行で済むので、それぞれに記述してしまいましょう。
1つ目は、変数tsの場合です。if文で10より小さいときに「0」を文字列として付け加えます。「0 + ts」だと、足し算になってしまいますが、文字列にすることで「0」が表示されます。
2つ目と3つ目も意味は同じです。それぞれ変数tmと変数thについて処理しています。

もしユーザー関数を使ったらどうなるでしょうか。
左の3行はこうなります。

ts = set_zero(ts);
tm = set_zero(tm);
th = set_zero(th);

更にユーザー変数set_zeroが必要です。

function set_zero(num) {
	if (num < 10) return "0" + num; 
	return num;
}

全部で7行も必要になりますので、これではかえって無駄が多くなってしまいます。

【この時点の sw.html を別枠で表示】

完成

これでストップウォッチの完成です。
タイマー機能は、ゲームのプログラムでは良く使いますので、覚えておくと便利です。

以下に改良した2つのスクリプトを載せておきます。
1つは、計測時間を0.1秒単位にしたもの。もう1つは、ラップタイムを計測できるものです。

計測単位を0.1秒にする(1)

ユーザー関数start_countにあるタイマーの呼び出し間隔を「1000」から「100」にします。

function start_count() { 
	if (sw_status == 0) { 
		document.form_sw.bstart.value = "ストップ";
		sw_status = 1;
		timerID = setInterval("count_up()",100); 
	} else {
		document.form_sw.bstart.value = "スタート";
		sw_status = 0;
		clearInterval(timerID);
	}
}

ユーザー関数count_upにある「timer++」を「timer = timer + 0.1」に変更しても良いのですが、「0.1」を10回足すと計算に誤差が生じるため、これはお奨めできません。
そこで、ここはそのまま「1」を足し続けます。

JavaScriptの演算誤差は、ブラウザによって異なります。
昔はExcelなど計算ソフトですら誤差がありました。これは10進数ではなく2進数で計算を行うため、小数を正しく扱えないという弱点によるものです。
計算ソフトはこの点を改善したものの、ブラウザでは対応が遅れています。

次に、ユーザー関数count_formatで、単位を調節しましょう。

function count_format(num) {
	var ts = num / 10;
	var tm = Math.floor(ts / 60);
	ts = ts % 60;
	var th = Math.floor(tm / 60);
	tm = tm % 60; 
	if (ts < 10) ts = "0" + ts;
	if (tm < 10) tm = "0" + tm;
	if (th < 10) th = "0" + th;
    return th + ":" + tm + ":" + ts;
}

1行目を修正して、「num % 60」を「num / 10」にします。10で割って、カウントした時間を0.1秒単位に直しています。
これで上記の誤差問題がなくなります。
次に、2行目の変数numを変数tsに変更します。
3行目で、変数tsから60で割った余りを出しています。

計測単位を0.1秒にする(2)

表示に1つ問題が生じていますので、これを修正したいと思います。
現時点では、「00:00:01.0」と表示すべきところが「00:00:01」と表示されてしまいます。
「.0」が表示されないのです。

ユーザー関数count_formatで、変数tsの値を修正します。

function count_format(num) {
	var ts = num / 10;
	var tm = Math.floor(ts / 60);
	ts = (ts % 60).toFixed(1);
	var th = Math.floor(tm / 60);
	tm = tm % 60; 
	if (ts < 10) ts = "0" + ts;
	if (tm < 10) tm = "0" + tm;
	if (th < 10) th = "0" + th;
	return th + ":" + tm + ":" + ts;
}

表示の小数点以下を揃えるための関数toFixedを使います。引数に「1」を指定すると小数点以下第1位(1桁目)に表示が揃います。

【この時点の sw.html を別枠で表示】

ラップタイムを表示する

<form name="form_sw">
<input name="counter" type="text">
<input name="bstart" type="button"value="スタート" onClick="start_count()">
<input name="breset" type="button"value="ラップ" onClick="lap()">
<input name="breset" type="button"value="リセット" onClick="reset_form()">
</form>
<script type="text/javascript"> 
sw_status = 0;
reset_form();

function reset_form() {
	if (sw_status == 1) {start_count();}
	timer = 0; 
	document.form_sw.counter.value = count_format(0);
}
 				
function start_count() { 
	if (sw_status == 0) { 
		document.form_sw.bstart.value = "ストップ";
		sw_status = 1;
		timerID = setInterval("count_up()",100); 
	} else {
 		document.form_sw.bstart.value = "スタート";
 		sw_status = 0;
 		clearInterval(timerID);
 		document.form_sw.counter.value = count_format(timer);
 	}
 }
 				
 function count_up() {
 	timer++;
 	if (sw_status < 2) {
 		document.form_sw.counter.value = count_format(timer);
 	}
 }
 				
function lap() {
	if (sw_status == 1) {
		sw_status = 2;
	} else  if (sw_status == 2) {
		sw_status = 1;
	}
}

function count_format(num) {
	var ts = num / 10;
 	var tm = Math.floor(ts / 60);
	var ts = (ts % 60).toFixed(1);
	var th = Math.floor(tm / 60);
	tm = tm % 60; 
	if (ts < 10) ts = "0" + ts;
	if (tm < 10) tm = "0" + tm;
	if (th < 10) th = "0" + th;
	return th + ":" + tm + ":" + ts;
}
</script>

まず、フォームにボタンを1つ足します。
「ラップ」ボタンです。押すとユーザー関数lupを呼び出します。

スクリプトは下から見ていきましょう。
まず、ユーザー関数lapです。
ここでは、変数sw_statusを2つのパターンで書き換えます。
1つは、「1」のときに「2」にすること、また、「2」のときに「1」にするというものです。
elseの部分がelse ifになっているのは、2つの条件をどちらか1つだけ判断させるためです。

もし、「else」だったら、変数sw_statusが「0」のときにも実行されてしまいます。

次にユーザー関数count_upを見てみましょう。
ここで、タイマーを表示しているので、変数sw_statusが「2」より小さいときに表示をするようにif文を書きます。
逆に言うと、「2」の時はラップを表示するため、タイマーの表示をしないということになるのです。
これで、ラップボタンを押す毎に、ラップの表示をし、表示中もタイマーを止めることなく表示を元に戻すことが出来ます。

最後に1つ工夫をするため、ラップ表示中にストップボタンが押されたとき、止めた時間を表示させます。
ユーザー関数start_countのタイマー停止の次に、変数timerを表示させる1行を追加します。これはユーザー関数count_upにある表示の行と同じものです。

【この時点の sw.html を別枠で表示】

改良したプログラム

大幅に改良して記録式ストップウォッチを作りました。 cookieという機能を使い、計測した時刻を10件分記録して表示します。

戻る