MENU

JavaScriptサンプルコード日付・時刻

【JavaScript】 サクッとストップウォッチを作ってみる

更新日:2021/04/28

 

JavaScriptでブラウザ上で動くストップウォッチを作成してみます。
今回は開始と停止だけでなく、一時停止からの再開やラップ測定も組み込んでみます。

 

完成デモ

ストップウォッチの基本的な考え方

 

ストップウォッチの時間計測は、setInterval()またはsetTimeout()を使用します。

 

setInterval( コールバック , 呼び出し時間 ) … 一定時間間隔で処理を呼び出す
setTimeout( コールバック , 呼び出し時間 ) …一定時間経過後に一回だけ処理を呼び出す

 

呼び出し時間はミリ秒で指定します。

 

指定時間後に一回だけコールバックを呼び出す場合は、setTimeout()を使用します。
一定時間ごとにコールバックを呼び出す場合はsetInterval()を使用しますが、setTimeout()を再使用することも可能です。

 

経過時間の計測

 

経過時間計測な次のようにおこないます。

 

  1. 開始時にDate.now()でタイムスタンプを取得する。
  2. タイマーを開始する
  3. タイマー呼び出し時にDate.now()でタイムスタンプを取得する。
  4. 3で取得したタイムスタンプから、2で取得したタイムスタンプを引き算する
  5. 4の値を経過時間として処理する
  6. タイマー呼び出しまで待ち、呼び出されたら3へ

 

この処理で、経過時間がミリ秒単位で取得できます。

 

呼び出し時間を加算することで、経過時間を積み上げるのはNGです。

 

NG

 


let time = 0; // 経過時間

   // 10ミリ秒ごとにタイマー呼び出し

setInterval( ()=>{
          //  10ミリ秒ごとに呼び出されるので10を加算
        time += 10; 
    }, 10 );

 

setTimeout()またはsetInterval()は、呼び出し時間で指定した時間ぴったりに、コールバックが呼び出すわけではありません。

 

メインの流れが終わった後、同じような待機処理を確認して、順番が回ってきたときに指定時間が経過していたらコールバックが呼び出されます。

 

また、ブラウザによっては呼び出し時間の最小値が設定されているケースがあります。
この場合は、それより小さい時間をセットしても最小値に丸められます。

 

解決方法として、上記の方法が採用されます。

 

 

一時停止/再開

 

ストップウォッチの一時停止時に、次の処理をおこないます。

 

  1. 一時停止までの経過時間を、積算時間として保存する

 

再開時は、次の処理をおこないます。

 

  1. 開始時にDate.now()でタイムスタンプを取得する。
  2. タイマーを開始する
  3. タイマー呼び出し時にDate.now()でタイムスタンプを取得する。
  4. 3で取得したタイムスタンプから、2で取得したタイムスタンプを引き算する
  5. 4の値に積算時間を加算した結果を経過時間として処理する
  6. タイマー呼び出しまで待ち、呼び出されたら3へ

 

この処理は経過時間の計測とほぼ同じです。
異なるのは4だけなので、経過時間の計測の処理は積算時間を0として計算するのが妥当な処理になります。

 

ミリ秒の数値を分:秒.ミリ秒で表示

 

ミリ秒を分:秒.ミリ秒で表示する場合、方法の一つとしてDateオブジェクトを使用する方法があります。

 

Dateオブジェクト使用例


const millisecond = 366100;

const date = new Date( millisecond );

const dateString = date.getMinutes() + ":" + date.getSeconds() + "." + date.getMilliseconds();
// 6:6.100

 

しかしストップウォッチは動作間隔が短いので、可能な限り処理効率を上げるのが望ましいです。

 

そこで、単純な計算式で分、秒、ミリ秒を求めます。

 

■分の計算

 

Math.floor( millisecond / 60000 )

 

■秒の計算

 

Math.floor( millisecond % 60000 / 1000)

 

■ミリ秒の計算

 

Math.floor( millisecond % 1000)

 

また桁合わせも必要です。

 

次にように数値を文字に変換し、先頭を"0"で穴埋めします。

 

Math.floor( millisecond / 60000).toString().padStart(2,"00")

 

穴埋めについては次の記事で紹介しているので読んでみてください。
【JavaScript】 ゼロやスペースで埋めして桁揃えする

ソースコード

 

今回は"開始/リセット"、"一時停止/再開"、"ラップ"の3つのボタンを用意しました。

 

■開始/リセット

 

タイマー停止中で一時停止でなければ、ストップウォッチをスタートします。
タイマー動作中または一時停止ならば、ストップウォッチを停止して表示をリセットします。

 

■一時停止/再開

 

タイマー動作中なら、ストップウォッチを一時停止します。
一時停止中なら、ストップウォッチを再開します。
これ以外は、何もしません。

 

■ラップ

 

タイマー動作中なら、現在の経過時間をラップ表示します。

 

この仕様を元に次のHTMLを作成しました。

 

HTML


<p><button id="start">開始/リセット</button><button id="pause">一時停止/再開</button><button id="wrap">ラップ</button></p>
<p id="watchArea"></p>
<textarea id="wrapArea"></textarea>

 

次はJavaScriptです。

 

JavaScript

 

"use strict";

window.addEventListener( "DOMContentLoaded" , ()=> {
    /**
     * @param watchCallBack 経過時間報告用コールバック
     * @param wrapCallBack ラップ報告用コールバック
     */
    const getStopWatch = function ( watchCallBack , wrapCallBack ){
        let accumulatedTime = 0,    // 積算時間
            currentTime=null,       // タイマー開示タイムスタンプ
            timerId=null;           // setInterval() の返り値

            // リセット処理
        const reset = () =>{
            timerOff(); accumulatedTime = 0; currentTime=null;
                // リセットされたことをnullで通知
            watchCallBack( null ); wrapCallBack ( null );
        };
            // 開始処理
        const start = () =>{ currentTime = Date.now();timerOn(); };
            // 一時停止処理
        const pause = () =>{
                // これまでの経過時間を退避
            accumulatedTime = getNowTime();
            timerOff();
            currentTime = null;
        };
            // 再開処理
        const resume = () =>start();
            // ラップ報告処理
        const wrap = () =>wrapCallBack( getNowTime() );
            // 経過時間の算出
        const getNowTime = () =>accumulatedTime + Date.now() - currentTime;

            // タイマー停止処理
        const timerOff = () => {
            if( timerId === null ) return;
            clearInterval(timerId);
            timerId = null;
        };
            // タイマー開始処理
        const timerOn = () => {
            if( timerId !== null ) clearTimeout(timerId);
            timerId = setInterval(()=>watchCallBack( getNowTime() ),10);
        };

        reset();

            // 必要な機能だけ返す
        return Object.freeze({
            start:()=> currentTime === null && accumulatedTime === 0 ?   start()  : reset(),
            pause:()=> currentTime === null ? ( accumulatedTime === 0 ? false : resume() ) : pause(),
            wrap:()=> currentTime === null ?  false : wrap(),
        });
    };

        // ミリ秒を画面表示する形式に変換
    const timeString = time =>`${
        Math.floor(time / 60000).toString().padStart(2,"00")
    }:${
        Math.floor(time % 60000 / 1000).toString().padStart(2,"00")
    }.${
        Math.floor(time % 1000).toString().padStart(3,"000").slice(0,2)
    }`;

    const watchArea = document.getElementById("watchArea");
    const wrapArea = document.getElementById("wrapArea");

    const stopWatchObj = getStopWatch(
         time => watchArea.textContent = time === null ? "00:00.00" : timeString( time ) ,
         time => wrapArea.value = time === null ? "" : wrapArea.value + "\n" + timeString( time )
    );

    const buttonDefine = [
            { id:"start" , listener:()=>stopWatchObj.start() },
            { id:"pause" , listener:()=>stopWatchObj.pause() },
            { id:"wrap"  , listener:()=>stopWatchObj.wrap()  }
        ];
    buttonDefine.forEach( e => document.getElementById(e.id).addEventListener("click",e.listener));
});

 

ストップウォッチの時間計測処理をオブジェクトとして独立させ、ボタンが押されたらメソッドを呼び出す流れになっています。

 

正確なストップウォッチはムリ

 

せっかくストップウォッチを作るのだから、正確なものにしたいですね。

 

しかしJavaScriptの特性上、ボタンが押されてからスクリプトのコードが呼び出されるまで待機時間があります。
この時間はその時の状況によって大きく変わるため、コード内で補正するのが難しいです。

 

またブラウザによっては、タイマー呼び出し時間の最低値が設定されているケースはあり、この場合はそれ以上の精度で計測できません。

 

つまり、JavaScriptでミリ秒単位で正確な時間計測をすることを目的にストップウォッチを作成するのはやめておくべきということです。

 

おおよその時間把握に使用しましょう。

 

 

音楽で集中力アップ!
通勤中の暇つぶしもOK!
amazon musicはスマホで
気ままに好きな音楽を聴けるのが魅力ですね♪


あなたはサラリーマン脳?

 

あなたは人の下でしか働けないというサラリーマン脳ではないですか?

まさかの時の備えは、今とは全く違う発想でないとあまり意味がありません。
また同じまさかがありますからね。

自分だけの力で収入を得られるという経験を、一度してみてください。
僕のように、あなたの人生観が大きく変わりますよ。


記事の内容について

 

こんにちはけーちゃんです。
説明するのって難しいですね。


「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。

そんなときは、ご意見もらえたら嬉しいです。

ご意見はこちら。
https://note.affi-sapo-sv.com/info.php

【お願い】

お願い

■このページのURL


■このページのタイトル


■リンクタグ