ストップウォッチの枠組みを作る
タイマー部分とStart、Stop、Resetボタンを用意し、ストップウォッチをつくります。
htmlの中身は
<div id="timer">00:00.000</div>
<button id="start">Start</button>
<button id="stop">Stop</button>
<button id="reset">Reset</button>
と、こんな感じにタイマー部分とそれを操作するボタンを配置。
まずは個々のボタンが動くようにする。
{
const timer = document.getElementById('timer');
const start = document.getElementById('start');
const stop = document.getElementById('stop');
const reset = document.getElementById('reset');
let startTime;
let timeoutId;
function countUp() {
const d = new Date(Date.now() - startTime);
const m = String(d.getMinutes()).padStart(2, '0');
const s = String(d.getSeconds()).padStart(2, '0');
const ms = String(d.getMilliseconds()).padStart(3, '0');
timer.textContent = `${m}:${s}.${ms}`;
timeoutId = setTimeout(() => {
countUp();
}, 10);
}
start.addEventListener('click', () => {
startTime = Date.now();
countUp();
});
stop.addEventListener('click', () => {
clearTimeout(timeoutId);
});
reset.addEventListener('click', () => {
timer.textContent = '00:00.000';
});
}
ざっと大枠だけ用意するとこうなります。
問題点を解決する
ただしこれだと一旦ストップしてから再スタートすると、00:00:000から始まってしまう。これを止めた時点から再スタートさせたい。
つまりストップした時点の経過時間を保持し、それを足してから再スタートさせれば良い。
経過時間をelapsedTimeと宣言し、0を入れておく(そうすることで最初のスタート時には影響されないようにする)
let startTime;
let timeoutId;
let elapsedTime = 0; // タイマーが走っていた時間 === 経過時間
ストップした時点での経過時間は`Date.now() - startTime`で計算して`elapsedTime`に入れる。
stop.addEventListener('click', () => {
clearTimeout(timeoutId);
elapsedTime = Date.now() - startTime; // ストップした時点での経過時間
});
次にスタート時に経過時間を加える。
const d = new Date(Date.now() - startTime + elapsedTime);
ただ、これでは、elapsedTimeが直近の経過時間しか保持していないため、ストップ・スタートを繰り返すと時間が巻き戻ってしまうという問題がある。
従って
elapsedTime += Date.now() - startTime; // ストップした時点での経過時間
のようにして、今までの経過時間を積み重ねるようにすれば良い。
これで良しと思いきや、リセットを押して再スタートした際は、前回ストップした状態から始まってしまうので、
reset.addEventListener('click', () => {
timer.textContent = '00:00.000';
elapsedTime = 0; // 経過時間のリセット
});
として、経過時間もリセットすることを忘れないようにする。
通常の操作以外で発生する問題点を解決する
ストップウォッチとしては動くのですが、いくつかの問題点を抱えたままです。
1.スタートを複数回押した後だと、一回ストップを押しても止まらない
2.ストップを複数回押した後だと、正しい秒数から再開されない
これらは主に、各ボタンを押すごとにstartTimeやelapsedTimeが多重起動してしまうことが原因です。
それらの解決方法は、必要のないボタンは押させないが良いでしょう。フォールトアボイダンスというやつですね。
ボタンの状態を管理する
必要のないボタンを押させない状態は.disabled = true;と設定します。
従って、各状態ごとの状況は
| Start | Stop | Reset | |
|---|---|---|---|
| 開始時(Initual) | false | true | true |
| カウント中(Running) | true | false | true |
| 停止時(Stopped) | false | true | false |
| ※true = 非表示 / false = 表示 |
となります。
これを設定するのは
function setButtonStateInitial() {
start.disabled = false;
stop.disabled = true;
reset.disabled = true;
}
function setButtonStateRunning() {
start.disabled = true;
stop.disabled = false;
reset.disabled = true;
}
function setButtonStateStopped() {
start.disabled = false;
stop.disabled = true;
reset.disabled = false;
}
setButtonStateInitial(); //初期設定
とし、各ボタンをクリックしたときの関数に、これらの状態へ移行するようにする。
start.addEventListener('click', () => {
setButtonStateRunning(); // Runningへ以降
startTime = Date.now();
countUp();
});
stop.addEventListener('click', () => {
setButtonStateStopped(); // Stoppedへ以降
clearTimeout(timeoutId);
elapsedTime += Date.now() - startTime;
console.log(elapsedTime);
});
reset.addEventListener('click', () => {
setButtonStateInitial(); // Initualへ以降
timer.textContent = '00:00.000';
elapsedTime = 0;
});
これで操作そのものの問題は概ね解決できたと思います。
続きは装飾部分について取り組みます。その2はこちら https://qiita.com/amanomunt/items/b667aaea307f1df6b104