JavaScriptで時間を数えてスタートとストップとリセットの三つのボタンで操作できるストップウォッチのプログラムを組む。
ストップウォッチの計測時間の表示
00:00.00.00
ストップウォッチのソースコード
HTML
<figure class="time">
<p class="counter">00:00.00.00</p>
<button class="start" type="button">スタート</button>
<button class="stop" type="button">ストップ</button>
<button class="reset" type="button">リセット</button>
</figure>
CSS
.time {text-align:center}
.counter {font-size:xx-large;margin-top:.83em;margin-bottom:.83em}
JavaScript
// タイマー部
let stop;
let progress;
let addition = 0;
const record = document.querySelector("p.counter");
// カウンター
function timer() {
const start = new Date().getTime();
stop = setInterval(function() {
progress = new Date().getTime() - start + addition;
const noms = progress / 1000;
const millisecond = progress ? ("0" + String(noms).split(".")[1]).slice(-2) : "00";
const nos = Math.trunc(noms);
const second = nos ? ("0" + (nos % 86400 % 3600 %60)).slice(-2) : "00";
const minute = nos >= 60 ? ("0" + Math.trunc(nos % 86400 % 3600 / 60)).slice(-2) : "00";
const hour = nos >= 360 ? ("0" + Math.trunc(nos % 86400 / 3600)).slice(-2) : "00";
if (progress < 86400) {
record.textContent = hour + ":" + minute + "." + second + "." + millisecond; } else {
record.textContent = "00:00.00.00"; clearInterval(stop); }}, 10); }
// ボタン部
const startButton = document.querySelector("button.start");
const stopButton = document.querySelector("button.stop");
const resetButton = document.querySelector("button.reset");
stopButton.disabled = true; resetButton.disabled = true;
// スタート
startButton.addEventListener("click", function() {
progress = 0; timer(); startButton.disabled = true; stopButton.disabled = false; resetButton.disabled = false; });
// ストップ
stopButton.addEventListener("click", function() {
clearInterval(stop); addition = progress; startButton.disabled = false; stopButton.disabled = true; resetButton.disabled = false; });
// リセット
resetButton.addEventListener("click", function() {
clearInterval(stop); progress = 0; record.textContent = "00:00.00.00"; addition = 0; startButton.disabled = false; stopButton.disabled = true; resetButton.disabled = true; });
時間を数えて表示するプログラムの概要
二つの時間、開始/startのDateと終了/progressのDateの値を反復処理が可能なsetInterval()メソッドで取得して差し引いた経過時間/progressを捉える。
経過時間はミリ秒単位で取得されるので、その上の時分秒に換算して個別に表示するけれども同時に不揃いの桁数があれば先頭を「0」で埋めて揃えるようにする。
ifの条件式の数字(86400秒/二十四時間)が来るまで経過時間を捉えながら時間を数えて越えると消去してclearInterval(stop)
で自動的に止まる。
addEventListenerでボタンからタイマーのスタートでtimer()
とストップでclearInterval(stop)
とaddition = progress
とリセットでclearInterval(stop)
とrecord.textContent = "00:00.00.00"
とaddition = 0
を行う。
時間を数えるプログラムの説明
- 時間を取得する方法:Date()とgetTime()
- 時間を数える方法:終了から開始の減算
- 算出する経過時間の単位について:Date
- ミリ秒から時分秒に換算する方法:trunc()
- 少数点以下を抜き取る方法:split()
- 時間単位の桁数を揃える方法:slice()
- 時間毎に繰り返して実行する方法:setInterval()
- 表示する経過時間の振り分け:三項演算子とif
- 計測時間を表示する方法:textContent
- ボタンを使用する方法:addEventListener()
- ボタンを反映させない方法:disabled
時間を取得する方法:Date()とgetTime()
JavaScriptで時間を扱うにはDate()コンストラクターをnew演算子でインスタンス化した日付や時間のDataオブジェクトからgetTime()メソッドで必要な値を取得する。
const time = new Date().getTime();
Dateから時間を取得するメソッドは個別に時のgetHours()や分のgetMinutes()や秒のgetSeconds()やミリ秒のmilliseconds()もある。
または二つを纏めたDate.now()を使うこともできる。
const time = Date.now();
簡単だけれどもDataオブジェクトの不要なインスタンス化を避けるのに役立つ。
時間を数える方法:終了から開始の減算
JavaScriptで時間を数えるには開始と終了の時間を取得して進んだ後者から進まない後者を引く。すると進んだ時間がどのくらいかが分かり、結果的に時間を数えたのと等しくなる。
const start = new Date(),getTime();
const end = new Date().getTime();
const progress = end - start;
// 終了時間の取得は経過時間の式に組み込める
const progress = new Date().getTime() - start;
開始時間と終了時間を取得して後者から前者をを引いて進んだ時間の長さを計って数える。
ボタンで止めたところから計測を続行できるように中断時間の加算も行う。
const progress = new Date().getTime() - start + addition;
additionは計測の開始時点では必要ないから予め「0」を代入しておいて影響しないようにする。
経過時間の変数のprogressは主にタイマーの関数で使うけれどもボタンでも使うために、中断時間の変数のadditionはボタンからタイマーの関数に値を受け渡すためにその外側のプログラムの冒頭で宣言している。
参考サイト
算出する経過時間の単位について:Date
Dateオブジェクトから得られる時間の単位は最短でミリ(千分の一秒)秒で、それよりも速い時間は分からない。
経過時間が短くて追い付かないと「0」しか得られなかったりする。
Performance APIのperformance.now()メソッドを使うとマイクロ(百万分の一)秒までさらに速い時間を計測することができる。
取得される値はミリ秒単位で、浮動少数点以下にマイクロ秒に相当する部分を示している。
ミリ秒よりも大きな単位の時分秒での算出はgetTime()メソッドを個別のもの/getHours()かgetMinutes()かgetSeconds()に変えると直ちにできる。
またはミリ秒単位の経過時間をそれぞれの長さで割っても導ける。
- 時=ミリ秒÷3600000
- 分=ミリ秒÷60000
- 秒=ミリ秒÷1000
例えば5000ミリ秒の経過時間を分で換算する場合は「5000 / 60000」という演算を行う。
参考サイト
ミリ秒から時分秒に換算する方法:trunc()
プログラムを簡素化するために時分秒は取得されたミリ秒を千で割ってもっと桁数の少ない秒に換算してから捉える。
const millisecond = progress;
const nos = Math.trunc(millisecond / 1000);
const second = nos % 86400 % 3600 %60;
const minute = Math.trunc(nos % 86400 % 3600 / 60));
const hour = Math.trunc(nos % 86400 / 3600));
ミリ秒の経過時間/progressを取得したら千で割ったものをtrunc()メソッドにかけて整数を取る。余りの少数点以下を切り捨てるfloor()メソッドでも結果は同じになる。すると秒に換算される。
秒の経過時間を使うと時分秒はそれぞれに一日/二十四時間当たりの必要な単位の秒数で割った余りの整数になる。
- 日単位の秒数=86400(24×60×60)
- 時単位の秒数=3600(60×60)
- 分単位の秒数=60(60×1)
- 秒単位の秒数=1(基準)
秒から必要な単位の秒数を使って時分秒に換算すると秒数は86400で割った余りを3600で割った余りを60で割った余り(最小単位だから整数でしかない)、分数は86400を割った余りを3600で割った余りを分毎の秒数(60)で割った余りの整数、時数は86400で割った余りを時毎の秒数(3600)で割った余りの整数として導きだされる。
少数点以下を抜き取る方法:split()
経過時間がミリ秒単位で得られてそのままだとミリ秒自体を表示できないので、時分秒と区別するために千で割って少数点以下に移す。
const noms = progress / 1000;
数値の少数点以下を抜き取るにはsplit()メソッドで少数点となる半角ピリオド(.)を指定して二つに分割された配列項目の二番目(1)を受け取る。
const millisecond = String(noms).split(".")[1];
するとミリ秒自体の一から三桁の数値が使用できるようになる。
時間単位の桁数を揃える方法:slice()
時分秒とミリ秒を二桁で表示するけど、計算して得られたままで使うと表示の幅が伸縮してしまうので、固定して見易く桁数を揃えるために足りない桁数に「1」ならば「01」と表示するように先頭に「0」を追加する。
const second = ("0" + ).slice(-2);
ゼロ埋め/パディングと呼ばれる数の表示の桁合わせだけど、得られた数値の先頭に最も必要な分だけ「0」を追加したものを作成してslice()メソッドでインデックスに「-」を付けて全て後ろから同じ分だけ切り取ると、丁度、足りない桁数だけが「0」で埋められた状態で得直せる。
時間毎に繰り返して実行する方法:setInterval()
ある範囲内で経過時間を何度も得るためには繰り返してプログラムを実行しなくてはならない。setInterval()メソッドを使うのが最も簡単な方法だと思うし、そのためにあるだから非常に使い易い。
function timer() {
const start = new Date().getTime();
stop = setInterval(function() {
// タイマーのプログラム
}, 10); }
setIntervalは関数などを引数に入れて繰り返して実行できる。時間を数える場合は必要な関数の内側で開始時間を取得した後にsetIntervalを配置して反復処理を行う。setIntervalの内側で開始時間を取得すると、毎回、基準となる時間が変わってしまう。反復処理の間の同じような時間しか数えられないから注意しなくてはならない。
半角コンマ(,)で区切った二つ目の引数に実行間隔の時間をミリ秒単位で指定する。ただし最短で「10」の十ミリ秒とされるようで、「1」を指定しても「10」として扱われて実行間隔は速くならないらしい。最長は
setIntervalは時間に関しては必ずしも正確ではない。繰り返されるプログラムの実行時間が加味されるから大きいと指定した時間よりも遅れる可能性がある。その他にも周りのタスクの状況によって動作が速やかでなくなる場合も出て来る――サンプルのストップウォッチもブログを読み込んだ直後に動かすとブラウザの処理が嵩んで少し止まったりする――から時間通りに使うには少なくとも実行間隔と扱うプログラムの処理速度のバランスを取るのが望ましい。
止めるためにはclearInterval()メソッドにsetInterval()メソッドの識別用の戻り値を使う。
stop = setInterval(function() {
// 実行するプログラム
}, 10); }
発火するときに変数に代入しておいて条件文やスイッチで使えるようにする。
関数などの実行時間を遅延するsetTimeout()メソッドを再帰的に使用しても同様の結果となる。
function timer() {
// 開始時間の取得
function count() {
// 経過時間の算出と表示
stop = setTimeout(count, 1); }
count(); }
止めるときもsetIntervalと同様の仕方だけど、すなわち当該のメソッドの識別用の戻り値によって対応するclearTimeout()メソッドを行う。
setTimeoutは「10」よりも速い遅延時間を設定できるからsetIntervalよりも短い間隔で動作し得る。
両者の反復処理の基本的な違いは間隔の取り方にある。
setIntervalは実行する関数などの処理時間が取られる間隔に含まれるけれどもsetTimeoutの再帰的な用法はそうではなく、実行する関数などの処理時間に続いて間隔が取られる。つまり前者は指定される時間が実際に取られる間隔に等しいけれども後者は指定される時間が関数などの処理時間に加えられて初めて取られる間隔に等しくなる。
普通に反復処理を行うかぎりはsetInterval()メソッドの方が一律の間隔を指定できて使い易い。または実行する関数などの処理時間に関わらず、指定される時間で間隔をしっかり空けたい場合にはsetTimeout()メソッドの再帰的な用法の方が有利だろう。
時間の計測を終了する変数のstopは主にタイマーの関数で使うけれどもボタンでも使うためにその外側のプログラムの冒頭で宣言している。
参考サイト
表示する経過時間の振り分け:三項演算子とif
四つの時間の単位、時と分と秒とミリ秒をどのように表示するかのプログラムは色々と考えられるけれどもなるべく短く軽く仕上げる方がsetIntervalの動作を遅らせず、正確さを多く保てる。
それぞれの表示する部分はあるかないか(単位毎に計測時間が到達したかどうか)に分けられるから変数を作る段階で、三項演算子でどちらかに振り分けて表示の仕方を確定した。
const minute = nos >= 60 ? ("0" + Math.trunc(nos % 86400 % 3600 / 60)).slice(-2) : "00";
分ならば60秒を越えると数値を、それまでは「00」を代入するので、一つの変数でどちらの場合でも使い分けられる。
if (progress < 86400) {
record.textContent = hour + ":" + minute + "." + second + "." + millisecond; } else {
record.textContent = "00:00.00.00"; clearInterval(stop); }
一日/二十四時間で停止してそれまで経過時間を表示するために条件分岐のif文で振り分けるけれども全ての単位毎の時間は三項演算子で先に振り分けてあるから表示するプログラム自体は一行で済ませられる。
止まるときは一日/二十四時間に到達して計測時間の表示はリセットされるので、元通りの「00:00.00.00」を書き込むと共にclearInterval(stop)
でsetIntervalによる繰り返しの実行を止める。
参考サイト
計測時間を表示する方法:textContent
時間を数えて表示する部分のHTML要素をquerySelector()メソッドで取得してtextContentで書き込んでいる。
// p要素のクラスcounterの取得
const record = document.querySelector("p.counter");
// 計測時間を書き込む
record.textContent = hour + ":" + minute + "." + second + "." + millisecond;
HTML要素の取得はクラスならばgetByElementsClassName()メソッドでも可能だ。
取得するのが一つのクラスだけだからquerySelector()メソッドが簡単かも知れない。
書き込みは通常の文書(プレーンテキスト)ならばinnerTextやinnerHTMLでも可能だ。
数字を書き込むだけだからtextContentが簡単かも知れない。
表示部分の変数のrecordは主にタイマーの関数で使うけれどもボタンでも使うためにその外側のプログラムの冒頭で宣言している。
参考サイト
ボタンを使用する方法:addEventListener()
ボタンのクリック/タッチからストップウォッチのプログラムを実行するためにquerySelector()で取得したボタンのHTML要素にaddEventListener()メソッドを「click」の引数で使う。
// ボタンのHTML要素を取得
const startButton = document.querySelector("button.start");
// addEventListenerの「click」のコールバックで実行
startButton.addEventListener("click", function() {
// ボタンで動かしたいプログラム
progress = 0; timer(); });
スタートボタン/startButtonはprogress = 0
で経過時間の値を初期化してtimer();
でストップウォッチを開始する。
ストップボタン/stopButtonはclearInterval(stop)
でストップウォッチを止める。そしてaddition = progress
でそこまで数えた時間を変数に格納して再びスタートボタンが押された場合に新たに数える時間に追加して続行できるようにする。
リセットボタンスライドresetButtonはclearInterval(stop)
で止めると同時にやり直せるようにprogress = 0
で経過時間の値を初期化しながら経過時間の表示も「00:00.00.00」と書き込んで元に戻す。
ボタンを反映させない方法:disabled
誤動作を防ぐために不要なボタンを使えないように止めたいので、三つのボタンのそれぞれにdisabledを設定する。
startButton.disabled = true;
startButton.disabled = false;
真偽値で、trueはボタンが使えず、falseはボタンが使える。
最初はスタートボタンだけ、開始したらストップボタンとリセットボタン、停止したらスタートボタンとリセットボタン、再開したらスタートボタンだけが使えるように設定する。
ストップウォッチのプログラムにはJavaScriptの色んな方法や考え方が含まれていて覚えておくと他の場合にも役立てられる。
コメント