JavaScriptでストップウォッチ作ってみる

参考

javascript でストップウォッチを作ってみる。忘備録

完成形

stopwatch.png

シンプルなストップウォッチです.

HTML

最低限必要な表示する領域とボタンを配置します.

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>StopWatch</title>
  </head>
  <body>
    <div id="timer">00:00:000</div>
    <button id="start">start</button> <button id="stop">stop</button>
    <button id="reset">reset</button>
    <script src="js/main.js"></script>
  </body>
</html>

JavaScript(ES6)でロジックを書く

const timer = document.getElementById("timer");
const start = document.getElementById("start");
const stop = document.getElementById("stop");
const reset = document.getElementById("reset");

// 経過時間を保存する変数(単位:ミリ秒)
let elapsedTime;
// スタートボタンを押したときのUnix Epoch
let startTime;
// タイマーのID
let timerId;
// 以前 stop したタイミングまでの計測時間
let timeToAdd = 0;

// 表示される内容をアップデートする関数
const updateTimeText = () => {
  // 1分 = 1000 ミリ秒 * 60秒
  let m = Math.floor(elapsedTime / (1000 * 60));
  // 1分に満たなかったミリ秒のうち,秒となったもの
  let s = Math.floor((elapsedTime % (1000 * 60)) / 1000);
  // 1秒になれなかったもの
  let ms = elapsedTime % 1000;

  // ゼロパディング
  m = `0${m}`.slice(-2);
  s = `0${s}`.slice(-2);
  ms = `00${ms}`.slice(-3);

  timer.textContent = `${m}:${s}:${ms}`;
};

// 経過時間の管理と計算を行う関数
const countUp = () => {
  timerId = setTimeout(() => {
    elapsedTime = Date.now() - startTime + timeToAdd;
    updateTimeText();
    countUp();
  }, 10);
};

start.addEventListener("click", () => {
  startTime = Date.now();
  countUp();
});

stop.addEventListener("click", () => {
  clearTimeout(timerId);
  timeToAdd += Date.now() - startTime;
});

reset.addEventListener("click", () => {
  elapsedTime = 0;
  timeToAdd = 0;
  // 00:00:000 を表示
  updateTimeText();
});

ポイント

const,let

定数は const ,変数は let を使います.

変数の意図しない変更を防ぐことができます.また,スコープが自身が定義されたブロックとなります.

アロー関数

毎度 function と書くのは面倒です. () => {} というように書くと見た目がすっきりします.

今回は関係ありませんが,this の扱いが少し違うので注意が必要です.

What is this value in JavaScript?

テンプレート文字列

"string text " + expression + " string text" は, `string text ${expression} string text` というように書くことができます.テンプレート文字列を使うことによってダブルクオートやシングルクオートを使う回数を減らすことができ,可読性が上がります.

デバッグ

上のコードにはバグがあるので修正します.

スタートボタンの連続クリック

このアプリケーションでは,連続で2回クリックをしたとき2つのタイマーイベント( countUp )が発生してしまいます.

この問題を解決する方法としていくつかのアプローチがあります.

  1. スタートボタンとストップボタンを統合する
  2. 連続で押せないように無効化する

一般的な(物理)ストップウォッチでは,1 の選択肢が取られています.

ただ,手法としては 2 もあるのでどちらも紹介します.

スタートボタンとストップボタンを統合する

HTML

HTML はスタートボタンとストップボタンが統合されたことにより,button タグが一つ減ります.

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>StopWatch</title>
  </head>
  <body>
    <div id="timer">00:00:000</div>
    <button id="start-stop">start</button> <button id="reset">reset</button>
    <script src="js/main.js"></script>
  </body>
</html>

JavaScript

現在の状態が動作中か停止中かを条件分けして,ボタンの処理を変える必要があります.

const timer = document.getElementById("timer");
const startStop = document.getElementById("start-stop");
const reset = document.getElementById("reset");

// 経過時間を保存する変数(単位:ミリ秒)
let elapsedTime;
// スタートボタンを押したときのUnix Epoch
let startTime;
// タイマーのID
let timerId = null;
// 以前 stop したタイミングまでの計測時間
let timeToAdd = 0;

// 表示される内容をアップデートする関数
const updateTimeText = () => {
  // 1分 = 1000 ミリ秒 * 60秒
  let m = Math.floor(elapsedTime / (1000 * 60));
  // 1分に満たなかったミリ秒のうち,秒となったもの
  let s = Math.floor((elapsedTime % (1000 * 60)) / 1000);
  // 1秒になれなかったもの
  let ms = elapsedTime % 1000;

  // ゼロパディング
  m = `0${m}`.slice(-2);
  s = `0${s}`.slice(-2);
  ms = `00${ms}`.slice(-3);

  timer.textContent = `${m}:${s}:${ms}`;
};

// 経過時間の管理と計算を行う関数
const countUp = () => {
  timerId = setTimeout(() => {
    elapsedTime = Date.now() - startTime + timeToAdd;
    updateTimeText();
    countUp();
  }, 10);
};

startStop.addEventListener("click", () => {
  // タイマーIDが停止中
  if (timerId === null) {
    startTime = Date.now();
    countUp();
    // ボタンのテキストをstartからstopに更新
    startStop.textContent = "stop";
  } else {
    // タイマーが動作中
    clearTimeout(timerId);
    timerId = null;
    timeToAdd += Date.now() - startTime;
    // ボタンのテキストをstopからstartに更新
    startStop.textContent = "start";
  }
});

reset.addEventListener("click", () => {
  elapsedTime = 0;
  timeToAdd = 0;
  // 00:00:000 を表示
  updateTimeText();
});

連続で押せないように無効化する

button タグには,disabled という属性を付けることができます. JavaScript から disabled 属性をコントロールすることによってボタンを無効化します.

HTML

はじめの HTML と同じコードですが,初期状態として stop ボタンを無効化しておきます.

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>StopWatch</title>
  </head>
  <body>
    <div id="timer">00:00:000</div>
    <button id="start">start</button> <button id="stop" disabled>stop</button>
    <button id="reset">reset</button>
    <script src="js/main.js"></script>
  </body>
</html>

JavaScript

はじめのコードに ボタンを有効化/無効化するコードを追加します.

const timer = document.getElementById("timer");
const start = document.getElementById("start");
const stop = document.getElementById("stop");
const reset = document.getElementById("reset");

// 経過時間を保存する変数(単位:ミリ秒)
let elapsedTime;
// スタートボタンを押したときのUnix Epoch
let startTime;
// タイマーのID
let timerId;
// 以前 stop したタイミングまでの計測時間
let timeToAdd = 0;

// 表示される内容をアップデートする関数
const updateTimeText = () => {
  // 1分 = 1000 ミリ秒 * 60秒
  let m = Math.floor(elapsedTime / (1000 * 60));
  // 1分に満たなかったミリ秒のうち,秒となったもの
  let s = Math.floor((elapsedTime % (1000 * 60)) / 1000);
  // 1秒になれなかったもの
  let ms = elapsedTime % 1000;

  // ゼロパディング
  m = `0${m}`.slice(-2);
  s = `0${s}`.slice(-2);
  ms = `00${ms}`.slice(-3);

  timer.textContent = `${m}:${s}:${ms}`;
};

// 経過時間の管理と計算を行う関数
const countUp = () => {
  timerId = setTimeout(() => {
    elapsedTime = Date.now() - startTime + timeToAdd;
    updateTimeText();
    countUp();
  }, 10);
};

start.addEventListener("click", () => {
  startTime = Date.now();
  countUp();
  // スタートボタンを無効化
  start.disabled = true;
  // ストップボタンを有効化
  stop.disabled = false;
});

stop.addEventListener("click", () => {
  clearTimeout(timerId);
  timeToAdd += Date.now() - startTime;
  // スタートボタンを有効化
  start.disabled = false;
  // ストップボタンを無効化
  stop.disabled = true;
});

reset.addEventListener("click", () => {
  elapsedTime = 0;
  timeToAdd = 0;
  // 00:00:000 を表示
  updateTimeText();
});

まとめ

プログラムは自分の書いたようにしか動きません. プログラムを書くにはまず必要な機能を考え言語化し,そのとおりにプログラムを書くと動きます.

記事一覧ページへ