JavaScriptを使ってストップウォッチを作成する　前半

　JavaScriptを使用してストップウォッチを作成する。具体的な仕様としては、
１　最初に画面に表示した時にはディスプレイに0が表示される。
２　スタートボタンを押すと、1秒ごとにディスプレイの表示が0,1,2・・・（開始からの秒数）と増えていく（カウントアップする、という）。
３　カウントアップ状態のときにストップボタンを押すと、ディスプレイの表示がその秒数で停止する。
４　再度スタートボタンを押すと、ディスプレイの表示が改めて0に戻り、0,1,2・・・と増えるカウントアップ状態に戻る。
５　カウントアップ状態になったときに、その時刻と、「開始」という表示がストップウォッチの下に操作ログとして追記される。
６　停止状態になったときに、その時刻を「終了」という表示がストップウォッチの下に操作ログとして追記される。

## ボタンを表示し、JavaScriptが動く準備をする
```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>StopWatch</title>
</head>
<body>
  <button class = "startButton">スタート</button>
  <button class = "stopButton">ストップ</button>
  <script src = "./main.js"></script>
</body>
</html>
```
```javascript:main.js
//startButtonというclassがついているタグ要素のうち、
//最初のもの（スタートボタン）を取り出す
let startButton = document.getElementsByClassName("startButton")[0];①

//取り出したstartButtonに対してクリックイベントのリスナを仕掛ける
startButton.addEventListener("click",function(){
  //この行はクリックしたときよばれる
  console.log("start")  
});
```
①はstartButtonというclassが指定されている要素のうち最初のものを取り出している。
流れとしては以下のようなものである。
１　document.getElementsByClassName("startButton")で、HTMLのドキュメントから、startButtonというクラスがついている要素を全て取得できる。
２　取得してきた値は配列と同じように操作できるので、document.getElemenetsByClassName("startButton")[0]で、一番初めに画面に出てきた要素を取り出した。
３　この要素は画面上でスタートボタンと呼ばれているボタンである。

startButton.addEventListnerでは、startButtonがクリックされたときのイベントリスナを仕掛けている。この箇所は対象のイベントが発生した時に動き出す仕組み（コールバック）になっている。

## 経過時刻をカウントし、見た目を整える
### スタートボタンが押されたら一定時間ごとにイベントを呼び出す
スタートボタンが押された時の動作を作成する。ここでは一定時間ごとに処理を繰り返すことができるsetIntervalを使う。setIntervalは第一引数の関数の内容を、第二引数の時間の間隔で何度も呼び出す機能がある。第二引数はミリ秒単位なので、ここでは1000を指定して、1秒ごとに呼び出されるようにする。②
```javascript:main.js
//startButtonというclassがついているタグ要素のうち、
//最初のもの（スタートボタン）を取り出す
let startButton = document.getElementsByClassName("startButton")[0];

//取り出したstartButtonに対してクリックイベントのリスナを仕掛ける
startButton.addEventListener("click",function(){
  //この行はクリックしたときよばれる
  console.log("start");
  let seconds = 0;①
  setInterval(function(){
    seconds++;
    console.log(seconds);
  },1000); ②
});
```
画面を更新してスタートボタンを押すと、コンソールにstartと表示されたあと、1,2,3・・・と表示されていくことが確認できる。
![8a3df99065b1f3fda6cde1ae576c6d96.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884270/7e60de5d-4a9e-bf5a-5be8-b1f6b3502e84.gif)
動作としては①で定義したsecondsの値が、１秒ごとに6行目の++で1ずつ加算されている。
<details><summary>setIntervalでの第一引数、第二引数とは？</summary>
setIntarvalは上記のように第一引数と第二引数が必要になるが、最初学習したときは、どれが第一引数で、どれが第二引数かわからなかった。そこで、もっと簡便化してかくと、こうなる。
setInterval(第一引数,第二引数)
なので、「,」で区切られている部分を探せば、どこが第二引数にあたるのかがわかる。今回でいえば、1000が第二引数である。ということは、
function(){
    seconds++;
    console.log(seconds);
  }
ここまでが第一引数ということになる。このfunctionの意味はすでに書いたので割愛。
</details>
### 数えた秒数を画面に表示する
今までは表示をコンソール上のみにしていたので、ブラウザ上にも秒数が表示されるようにする。
```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>StopWatch</title>
</head>
<body>
  <div class = "display">0</div>①
  <button class = "startButton">スタート</button>
  <button class = "stopButton">ストップ</button>
  <script src = "./main.js"></script>
</body>
</html>
```
スタートボタンの上にdivタグを入れて、classをdisplayとした。①
初期値として0を表示したいので、0を入れている。
以下のJavaScriptのコードでは、この新しく追加した要素をdocument.getElementsByClassNameでdisplayElmとして取得する。②
setIntervalの中で、displayElmのinnerTextを呼び出すことで、タグに囲まれている文字列（htmlの①の部分）を秒数の数字に変更している。

```javascript:main.js
//startButtonというclassがついているタグ要素のうち、
//最初のもの（スタートボタン）を取り出す
let displayElm = document.getElementsByClassName("display")[0];②
let startButton = document.getElementsByClassName("startButton")[0];

//取り出したstartButtonに対してクリックイベントのリスナを仕掛ける
startButton.addEventListener("click",function(){
  //この行はクリックしたときよばれる
  console.log("start");
  let seconds = 0;
  setInterval(function(){
    seconds++;
    displayElm.innerText = seconds;③
    console.log(seconds);
  },1000); 
});
```
その結果、スタートしてからの秒数が表示されることを確認できた。クラスdisplayのついた要素に囲まれた値が0,1,2・・・と順番に増える動きが実現できた。停止ボタンが機能していないので、画面をリロードするしかないが、動きとしては想定したものに近くなってきた。
![389cf84e06ac2366cf9e02138021c6a4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884270/1df72412-ed4f-f0f7-a7f5-fb1ffaccc415.gif)

### 見た目を整える
動きが整ってきたので、cssで装飾する。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>StopWatch</title>
  <link href = "./main.css" rel = "stylesheet">
</head>
<body>
  <h1 class = "title">ストップウォッチ</h1>
  <div id = "stopWatchPanel">
   <div class = "display">0</div>
   <div class = "actions">
     <button class = "startButton">スタート</button>
     <button class = "stopButton">ストップ</button>
   </div>
  </div>
  <script src = "./main.js"></script>
</body>
</html>
```

```css:main.css
.title {
  text-align: center;
}

#stopWatchPanel {
  margin: 0 auto;
  width: 10em;
}

.display {
  font-weight: bold;
  text-align: right;
  height: 1.5em;
  margin-bottom: .2em;
  padding: .5em;
  color: lightblue;
  background-color: black;
}

.actions {
  text-align: center;
  margin-bottom: 1em;
}

.message {
  text-align: center;
  border-bottom: 1px solid lightblue;
}
```
![c0bd4c96a589bf244ba8b46457013dfd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884270/94e1a3aa-cd02-fb96-25bf-df72ac93563a.png)

段々形になってきた。

## カウント停止を実現し、バグに対応する
### ストップボタンが押されたらイベントを停止する
ストップボタンの動作では、スタートボタンで動かし始めたsetIntervalを停止させることが必要である。その方法も**ブラウザから提供されている。**
まず、setIntervalは呼び出したときに「開始した繰り返し処理を識別する数値」を返す（intervalIDという）。

<details><summary>「開始した繰り返し処理」について</summary>

今回でいうと、「開始した繰り返し処理」とは、
```javascript
function(){
    seconds++;
    displayElm.innerText = seconds;③
    console.log(seconds);
  }
```
のことである。つまり、「displayというクラスがついた一番最初の文字（つまり0）」を、secondsを足していってinnerTextに反映させてね。」という**関数を繰り返し処理しているのである。**ここが非常に理解が難しかったところで、「繰り返し処理」が2つあるところで混乱した。
1つ目の繰り返し処理が、先ほどの関数内の繰り返し処理。2つ目が、関数そのものを一定の時間間隔で繰り返すというもの。
さて、これらをまとめると、「setIntervalは、開始した繰り返し処理　＝　functionそのものを繰り返すということになる。

</details>
このsetntervalIdを保持しておくことで、後で繰り返し処理を止めることができる。
setntervalにはいくつか書き方があるが、よく使われる形として、次のようなものがある。

```javascript
let intervalID = setInterval([一定時間で動かしたい処理の入っている関数],[動作の間隔])
```
そして、intervalIDを指定してclearntervalを呼び出すとその繰り返し処理を停止することができる。
```
clearInterval(intervalID)
```
setInterval、clearIntervalを組み合わせてコードを書いていく。setntervalの数値を入れておくための変数としてtimerという変数を準備する。①
setntervalの戻り値をtimerに代入しておくことで、intervalIDを持ち続けることができる。

```javascript:main.js
//startButtonというclassがついているタグ要素のうち、
//最初のもの（スタートボタン）を取り出す
let displayElm = document.getElementsByClassName("display")[0];
let timer = null;①

let startButton = document.getElementsByClassName("startButton")[0];
//取り出したstartButtonに対してクリックイベントのリスナを仕掛ける
startButton.addEventListener("click",function(){
  //この行はクリックしたときよばれる
  console.log("start");
  let seconds = 0;
  timer = setInterval(function(){②
    seconds++;
    displayElm.innerText = seconds;
    console.log(seconds);
  },1000); 
});

let stopButton = document.getElementsByClassName("stopButton")[0];③
stopButton.addEventListener("click",function(){③
clearInterval(timer);③
timer = null;③
});③
```
timer変数に入っている値は次の通り。
| アプリケーションの状態 | timer変数の値 |
|:-:|:-:|
| 初期状態  | null  |
| カウントアップ状態  | intervalIDとして扱われる何らかの値  |
| 停止状態  | null  |

特に停止状態のときにtimerを初期状態のnullに戻しておくと、「timer変数に値が入っているならカウントアップ状態」ということがわかりやすくなる。

次に、ストップボタンを押した時のイベントリスナを実装する。ボタンの要素を取得してくるところや、リスナを仕掛けるところはスタートボタンと同様である。ストップボタンをクリックされたらclearIntervalを呼び出して、timer変数にnullを代入する。
![411ac378bec92ec7e7e42271641f9a2d.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884270/d9069bc5-dcdd-5d35-fc86-77fe16dfc70b.gif)

### デバッグ（debug）する
ストップウォッチがだいぶ完成してきたところで、以下のような状況に出くわした。
・まれにストップを押してもなぜかタイマーが止まらない
・まれにカウントアップが1秒以下のタイミングで起きてしまう
・どちらもリロードするとなおる
![4b66e26702990b34db0bacd97f9e4745 (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884270/cad3e65d-2f9c-7e45-4452-f965acf90c88.gif)

これらは想定していない動きなので、バグ（bug）と呼ばれる。このようなバグを突き止めて治すことをデバッグ（debug）という。
ボタンとタイマーの制御に関連するところが原因だと思われるので、①の「新しくタイマーが開始されたところ（関数の終了直前）」でログを出し、②の「これからタイマーを止める場所」でもconsole.logをだす。
どちらも timerの値を確認できるようにしている。
```javascript:main.js
//startButtonというclassがついているタグ要素のうち、
//最初のもの（スタートボタン）を取り出す
let displayElm = document.getElementsByClassName("display")[0];
let timer = null;

let startButton = document.getElementsByClassName("startButton")[0];
//取り出したstartButtonに対してクリックイベントのリスナを仕掛ける
startButton.addEventListener("click",function(){
  //この行はクリックしたときよばれる

  let seconds = 0;
  timer = setInterval(function(){
    seconds++;
    displayElm.innerText = seconds;
    console.log(seconds);
  },1000); 
  console.log("start" + timer);①
});

let stopButton = document.getElementsByClassName("stopButton")[0];
stopButton.addEventListener("click",function(){
  console.log("stop" + timer);②
clearInterval(timer);
timer = null;
});
```
)
バグを再現する状態ができたときのconsole.lohの結果は以下のようになった。
![32865d454123735245f2a82c706f69d0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884270/dace7dc6-b454-b9bf-5717-0e89c361b5a4.png)
今回の内容は、「スタートを押して、ストップを押す前にさらにスタートを押す」とバグが起きることがわかった。
プログラムの中の動きで表現すると、「タイマーが動いている最中に、さらにタイマーを起動した」ときにバグが起きていると解釈できる。タイマーの動作はtimer変数一つで管理しているので、連続でスタートボタンを押すと前のタイマーのintervalIDが上書きされて、先に動かしたタイマーを止められないということがわかる。
これは当初考えていなかった仕様なので、次のようにする。

・タイマーが動いている時にスタートボタンを押しても何もしない
・タイマーが停まっている時にストップボタンを押しても何もしない

先の表にボタンの動作も付け加えると次のようになる。
| アプリケーションの状態 | timer変数の値 | スタートボタンの動作 | ストップボタンの動作 |
|:-:|:-:|:-:|:-:|
| 初期状態  | null  | カウントアップ開始  | 何もしない  |
| カウントアップ状態  | intervalIDとして扱われる何らかの数値  | 何もしない  | 停止する  |
| 停止状態  | null  | カウントアップ開始  | 何もしない  |

これをコードの言葉で表現すると、
・もしもtimer変数がnullならば、startButtonのクリックが動作する
・もしもtimer変数がnullでなければ、 stopButtonのクリックが動作する

と書き換えられる。つまりif文を使うだけで実現できそうである。

```javascript:main.js
//startButtonというclassがついているタグ要素のうち、
//最初のもの（スタートボタン）を取り出す
let displayElm = document.getElementsByClassName("display")[0];
let timer = null;

let startButton = document.getElementsByClassName("startButton")[0];
//取り出したstartButtonに対してクリックイベントのリスナを仕掛ける
startButton.addEventListener("click",function(){
  //この行はクリックしたときよばれる
  if(timer === null){
  let seconds = 0;
  timer = setInterval(function(){
    seconds++;
    displayElm.innerText = seconds;
    console.log(seconds);
  },1000); 
 }
  console.log("start" + timer);
});

let stopButton = document.getElementsByClassName("stopButton")[0];
stopButton.addEventListener("click",function(){
  if(timer !== null){
  console.log("stop" + timer);
clearInterval(timer);
timer = null;
  }
});
```

演算子が「===」というのは、厳密等価演算子という演算子で、型変換することなく厳密に等価比較を行うことができる。
これで先ほどのバグが修正された。


次回に続く。



> 参考文献
[ステップアップJavaScript フロントエンド開発の初級から中級へ進むために](https://www.amazon.co.jp/%E3%82%B9%E3%83%86%E3%83%83%E3%83%97%E3%82%A2%E3%83%83%E3%83%97JavaScript-%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89%E9%96%8B%E7%99%BA%E3%81%AE%E5%88%9D%E7%B4%9A%E3%81%8B%E3%82%89%E4%B8%AD%E7%B4%9A%E3%81%B8%E9%80%B2%E3%82%80%E3%81%9F%E3%82%81%E3%81%AB-%E4%BD%90%E8%97%A4-%E6%AD%A3%E5%BF%97-ebook/dp/B09MQ53Y2C/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=9G912ZQLVXY8&keywords=%E3%82%B9%E3%83%86%E3%83%83%E3%83%97%E3%82%A2%E3%83%83%E3%83%97JavaScript&qid=1669644043&qu=eyJxc2MiOiIxLjEwIiwicXNhIjoiMS4wMCIsInFzcCI6IjAuOTcifQ%3D%3D&sprefix=%E3%82%B9%E3%83%86%E3%83%83%E3%83%97%E3%82%A2%E3%83%83%E3%83%97javascript%2Caps%2C293&sr=8-1)