javascriptでタイマーを作る

# 作ったもの
タイマー

# 使った技術
html, css, javascript

# リンク
https://timer-1c484.web.app/timer.html

# 機能概要
- カウントダウンできる
- 測りたい時間を設定できる
- 時間が経過したらアラームが鳴る
- 途中でカウントダウンをストップできる
- ストップした後、キャンセルボタンを押すとセットした時間に戻る


# 仕様
## 項目定義
### タイマー
- 時間(hours)
    - min: 0
    - MAX: 23
- 分(minutes)
    - min: 0
    - MAX: 59
- 秒(seconds)
    - min: 0
    - MAX: 59

   

## ボタン定義
- Start
    - hours ≠ 0 かつ minutes ≠ 0 かつ seconds ≠ 0のとき、Start ボタンを押下すると、カウントダウンが始まる
    - hours = 0 かつ minutes = 0 かつ seconds = 0のとき、Start ボタンを押下しても、画面上何も変化は起きない
    - ボタンを押下してカウントダウンが始まると、Stopボタンに変わる
- Stop
    - カウントダウンが実行中のとき、ボタンを押下するとカウントダウンが中断する
    - Stopボタンが押下されると、自動的にRestartボタンに変わる
- Restart
    - カウントダウンが中断中のとき、ボタンを押下するとカウントダウンが再開する
    - Restartボタンが押下されると、自動的にStopボタンに変わる
- Reset
    - カウントダウン開始前、または実行中は、ボタンを押下できず、背景がグレーになっている
    - Stopボタンを押してカウントダウンが止まっているとき、ボタンの背景が青になり、押下できるようになる
    - Resetボタンを押すと、以下の通りの動作となる
        - タイマーがセレクトボックスに戻り、値は0時間0分0秒に戻る
        - RestartボタンがStartボタンに戻る
        - Resetボタンは背景がグレーになり、押下できなくなる

## 詳細設計
### 変数
- second ... セレクトボックスに入力された秒の値を取得
- minute ... セレクトボックスに入力された分の値を取得
- hour ... セレクトボックスに入力された時間の値を取得
- s ... secondを数値に変換して格納
- m ... minuteを数値に変換して格納
- h ... hourを数値に変換して格納
- time ... カウントダウン用変数 (s, m, hを合計して秒数に変換)

### 関数
- startCount()
    - 時間をセレクトボックスから設定し、スタートボタンを押したときに動作する
    - セレクトボックスの～時間 ～分 ～秒の数値をそれぞれdocument.getElementByIdで取得し、変数h, m, sに格納
    - 変数h, m, sを通して設定された時間を秒数に計算し直し、変数timeに格納
    - countStart()を呼び出し

- countStart()
    - timeが0以上の時:
        - changeButtonToStopを呼び出す (StartボタンをStopボタンに変更する)
        - 時間表示を上書く
        - countEverySecを定義し、setInterval()を格納
        - 1000ミリ秒ごとに、関数countDown()を実行する

- countDown()
    - time>0のとき
        - timeが1減少する
        - 変数h, m, sの値を上書き
            - h (時間) = time/3600(切り捨て)
            - m (分) = time/60(切り捨て) - h×60
            - s (秒) = time-h*3600-m*60
    - それ以外のとき
        - 繰り返し動作(countEverySec)を終了する
        - alertで”Time up!”という文字列を表示させる
        - タイムアップ時の音声を鳴らす
        - タイマー表示をセレクトボックスに戻す
        - StopボタンをStartボタンに戻す

- stopCount()
    - Stopボタンが押されたときに実行される
    - 繰り返し動作(countEverySec)を終了する
    - 2つ配置されているボタンを"Restart"ボタン、"Reset"ボタンに変える (=StopボタンをRestartボタンに変える)

- changeButtonToStart()
    - 2つ配置されているボタンを"Start"ボタン、"Reset"ボタンに変える

- changeButtonToStop()
    - 2つ配置されているボタンを"Stop"ボタン、"Reset"ボタンに変える

- restartCount()
    - カウントダウンがStopされ、Restartボタンが押されたときに実行
    - countStart()を呼び出す

- reviseCountInput()
    - タイマーをカウントダウン表示からセレクトボックス表示に変更する
    - changeButtonToStart()を呼び出す

# ソース
<details><summary>html</summary><div>

```timer.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Timer</title>
    <link rel="stylesheet" type="text/css" href="style/timer.css">
    <link rel="shortcut icon" href="img/favicon.ico">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script type="text/javascript" src="js/timer.js"></script>

</head>

<body>
    <div class="top_area"></div>
    <div class="name">Timer</div>
    <div class="middle_area1"></div>

    <div id="timer">
      <select id="hours" class="timer_contents">
        <option>0</option>
        <option>1</option>
        <option>2</option>
        <option>3</option>
        <option>4</option>
        <option>5</option>
        <option>6</option>
        <option>7</option>
        <option>8</option>
        <option>9</option>
        <option>10</option>
        <option>11</option>
        <option>12</option>
        <option>13</option>
        <option>14</option>
        <option>15</option>
        <option>16</option>
        <option>17</option>
        <option>18</option>
        <option>19</option>
        <option>20</option>
        <option>21</option>
        <option>22</option>
        <option>23</option>
      </select>
      <p class="timer_contents"> 時間</p>
      <select id="minutes" class="timer_contents">
        <option>0</option>
        <option>1</option>
        <option>2</option>
        <option>3</option>
        <option>4</option>
        <option>5</option>
        <option>6</option>
        <option>7</option>
        <option>8</option>
        <option>9</option>
        <option>10</option>
        <option>11</option>
        <option>12</option>
        <option>13</option>
        <option>14</option>
        <option>15</option>
        <option>16</option>
        <option>17</option>
        <option>18</option>
        <option>19</option>
        <option>20</option>
        <option>21</option>
        <option>22</option>
        <option>23</option>
        <option>24</option>
        <option>25</option>
        <option>26</option>
        <option>27</option>
        <option>28</option>
        <option>29</option>
        <option>30</option>
        <option>31</option>
        <option>32</option>
        <option>33</option>
        <option>34</option>
        <option>35</option>
        <option>36</option>
        <option>37</option>
        <option>38</option>
        <option>39</option>
        <option>40</option>
        <option>41</option>
        <option>42</option>
        <option>43</option>
        <option>44</option>
        <option>45</option>
        <option>46</option>
        <option>47</option>
        <option>48</option>
        <option>49</option>
        <option>50</option>
        <option>51</option>
        <option>52</option>
        <option>53</option>
        <option>54</option>
        <option>55</option>
        <option>56</option>
        <option>57</option>
        <option>58</option>
        <option>59</option>
      </select>
      <p class="timer_contents"> 分</p>
      <select id="seconds" class="timer_contents">
        <option>0</option>
        <option>1</option>
        <option>2</option>
        <option>3</option>
        <option>4</option>
        <option>5</option>
        <option>6</option>
        <option>7</option>
        <option>8</option>
        <option>9</option>
        <option>10</option>
        <option>11</option>
        <option>12</option>
        <option>13</option>
        <option>14</option>
        <option>15</option>
        <option>16</option>
        <option>17</option>
        <option>18</option>
        <option>19</option>
        <option>20</option>
        <option>21</option>
        <option>22</option>
        <option>23</option>
        <option>24</option>
        <option>25</option>
        <option>26</option>
        <option>27</option>
        <option>28</option>
        <option>29</option>
        <option>30</option>
        <option>31</option>
        <option>32</option>
        <option>33</option>
        <option>34</option>
        <option>35</option>
        <option>36</option>
        <option>37</option>
        <option>38</option>
        <option>39</option>
        <option>40</option>
        <option>41</option>
        <option>42</option>
        <option>43</option>
        <option>44</option>
        <option>45</option>
        <option>46</option>
        <option>47</option>
        <option>48</option>
        <option>49</option>
        <option>50</option>
        <option>51</option>
        <option>52</option>
        <option>53</option>
        <option>54</option>
        <option>55</option>
        <option>56</option>
        <option>57</option>
        <option>58</option>
        <option>59</option>
      </select>
      <p class="timer_contents"> 秒</p>
    </div>
    <div class="middle_area2"></div>
    <div class="buttons" id="buttons">
      <input type="button" class="button" id="startButton" value="Start" onclick=startCount()>
      <input type="button" class="button" id="resetButton" value="Reset" onclick=resetCount() disabled="true">
    </div>
    <audio id="timeUp" preload="auto">
      <source src="sound/alarm.mp3" type="audio/mp3">
    </audio>

    <!-- To avoid mobile audio issue -->
    <iframe src="sound/500-milliseconds-of-silence.mp3" allow="autoplay" id="silenceAudio" style="display:none"></iframe>
    
</body>
</html>
```
</div></details>


<details><summary>css</summary><div>

```timer.css
body{
    margin: 0px;
    font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
    color: #636363;
}

.top_area {
    height: 50px;
    background-color: #FFFFFF;
}

.name{
    font-size: 28px;
    text-align: center;
}

.middle_area1{
    height: 50px;
    background-color: #FFFFFF;
}

#timer {
    font-size: 32px;
    text-align: center;
}

.timer_contents{
    display: inline-block;
    font-size: 24px;
}

.middle_area2{
    height: 20px;
    background-color: #FFFFFF;
}

.buttons{
    text-align: center;
}

.button:enabled{
    display: inline-block;
    width: 80px;
    height: 30px;
    background: #668ad8;
    margin: 0px 8px;
    font-size: 16px;
    color: #FFFFFF;
    border-radius: 6px;
    border-bottom: solid 4px #627295;
}

.button:disabled{
    display: inline-block;
    width: 80px;
    height: 30px;
    background: #a0a0a0;
    margin: 0px 8px;
    font-size: 16px;
    color: #FFFFFF;
    border-radius: 6px;
    border-bottom: solid 4px #627295;
}

```
</div></details>

<details><summary>js</summary><div>

```timer.js

function startCount(){
	const second = document.getElementById("seconds").value;
	const minute = document.getElementById("minutes").value;
	const hour = document.getElementById("hours").value;
	s = Number(second);
	m = Number(minute);
	h = Number(hour);

	time = h*3600+m*60+s;
	console.log(h*3600+m*60+s);

	countStart();
}	

function countStart(){
	if (time>0) {
		changeButtonToStop();
		document.getElementById('timer').innerHTML = '<div id="timer">' + h + "時間 " + m +"分 " + s +"秒" + '</div>';	
		countEverySec = setInterval(countDown, 1000);
	}		
}



function countDown(){
	if (time>0) {
		time --;
		h = Math.floor(time/3600);
		m = Math.floor(time/60)-h*60;
		s = time-h*3600-m*60;
		document.getElementById('timer').innerHTML = '<div id="timer">' + h + "時間 " + m +"分 " + s +"秒" + '</div>';
	} else {
		clearInterval(countEverySec);
		document.getElementById("timeUp").play();
		alert("Time up!");
		reviseCountInput();
		changeButtonToStart();
	}
}

function stopCount(){
	clearInterval(countEverySec);
	document.getElementById("buttons").innerHTML = '<div class="buttons"><input type="button" class="button" id="restartButton" value="Restart" onclick=restartCount()><input type="button" class="button" id="resetButton" value="Reset" onclick=resetCount()></div>';
}

function changeButtonToStop(){
	document.getElementById("buttons").innerHTML = '<div class="buttons"><input type="button" class="button" id="stopButton" value="Stop" onclick=stopCount()><input type="button" class="button" id="resetButton" value="Reset" onclick=resetCount() disabled="true"></div>';
}

function changeButtonToStart(){
	document.getElementById("buttons").innerHTML = '<div class="buttons"><input type="button" class="button" id="startButton" value="Start" onclick=startCount()><input type="button" class="button" id="resetButton" value="Reset" onclick=resetCount() disabled="true"></div>';
}

function restartCount(){
	countStart();
}


function resetCount(){
	clearInterval(countEverySec);
	reviseCountInput();
}

function reviseCountInput(){
	document.getElementById("timer").innerHTML = '<div id="timer"><select id="hours" class="timer_contents"><option>0</option><option>1</option><option>2</option><option>3</option><option>4</option><option>5</option><option>6</option><option>7</option><option>8</option><option>9</option><option>10</option><option>11</option><option>12</option><option>13</option><option>14</option><option>15</option><option>16</option><option>17</option><option>18</option><option>19</option><option>20</option><option>21</option><option>22</option><option>23</option></select>'+' '+'<p class="timer_contents">時間</p>'+' '+'<select id="minutes" class="timer_contents"><option>0</option><option>1</option><option>2</option><option>3</option><option>4</option><option>5</option><option>6</option><option>7</option><option>8</option><option>9</option><option>10</option><option>11</option><option>12</option><option>13</option><option>14</option><option>15</option><option>16</option><option>17</option><option>18</option><option>19</option><option>20</option><option>21</option><option>22</option><option>23</option><option>24</option><option>25</option><option>26</option><option>27</option><option>28</option><option>29</option><option>30</option><option>31</option><option>32</option><option>33</option><option>34</option><option>35</option><option>36</option><option>37</option><option>38</option><option>39</option><option>40</option><option>41</option><option>42</option><option>43</option><option>44</option><option>45</option><option>46</option><option>47</option><option>48</option><option>49</option><option>50</option><option>51</option><option>52</option><option>53</option><option>54</option><option>55</option><option>56</option><option>57</option><option>58</option><option>59</option></select>'+' '+'<p class="timer_contents">分</p>'+' '+'<select id="seconds" class="timer_contents"><option>0</option><option>1</option><option>2</option><option>3</option><option>4</option><option>5</option><option>6</option><option>7</option><option>8</option><option>9</option><option>10</option><option>11</option><option>12</option><option>13</option><option>14</option><option>15</option><option>16</option><option>17</option><option>18</option><option>19</option><option>20</option><option>21</option><option>22</option><option>23</option><option>24</option><option>25</option><option>26</option><option>27</option><option>28</option><option>29</option><option>30</option><option>31</option><option>32</option><option>33</option><option>34</option><option>35</option><option>36</option><option>37</option><option>38</option><option>39</option><option>40</option><option>41</option><option>42</option><option>43</option><option>44</option><option>45</option><option>46</option><option>47</option><option>48</option><option>49</option><option>50</option><option>51</option><option>52</option><option>53</option><option>54</option><option>55</option><option>56</option><option>57</option><option>58</option><option>59</option></select>'+' '+'<p class="timer_contents">秒</p></div>'
	changeButtonToStart();
}

```
</div></details>

# 参考にしたリンク
- 1秒ごとに同じ動作を実行する
    - javascript - setInterbal(), clearInterbal())
        - https://www.nishishi.com/javascript-tips/setinterval-passage.html
        - https://www.nishishi.com/javascript-tips/realtime-countdown-deadline.htm
- Resetボタンの押下可能/不可によって表示を出しわける
    - css - enabled/disabled
        - http://www.htmq.com/selector/disabled.shtml
- ブラウザを使って音を出す
    - html5 - audio
        - https://allabout.co.jp/gm/gc/385187/
    - タイマー音 フリー素材
        - https://otologic.jp/


# 解決できていないこと
タイムアップ時の音に関して、PCので実行した際には出るが、iPhoneのSafari, Chromeだと出ない。
以下サイトを参考に実装しているが、上記問題は未解決。
https://webty.jp/staffblog/production/post-907/
そもそもクリック等をトリガーとしないと、iPhoneではブラウザから音が出ない仕組みの様子。