タイマー処理

【JavaScript】 カウントダウンタイマーをブラウザに設置してみる

更新日:2023/02/06

「後〇〇分で終了」なんて聞くと、自分も参加してくなります。
これをWebページやるときはカウントダウン表示すると、効果的ですね。

そこで今回は、ブラウザ上にカウントダウンタイマーを設置する方法を紹介します。

 

カウントダウンの主要となる関数を作成

カウントダウンのタイマー管理および残り時間と終了を通知する関数を作成します。
ブラウザへの表示はここではおこなわないことで、汎用性を確保しています。

なお経過時間の計測について次の記事で紹介しているので、確認してみてください。
【JavaScript】 経過時間や日数を取得し処理時間などを計測

カウントダウン主要コード


const coutdownTimer =  ( tickCallBack , endCallBack
    ,daysOrSeconds , hours = null , minutes  = null , seconds = null ) => {

        // 秒を日数・時間・分・秒に変換
    const calcTime = sec =>{
        const days = Math.floor(sec / 86400);
        const hours = Math.floor( (sec-=days*86400) / 3600);
        const minutes = Math.floor( (sec-=hours*3600) / 60);
        sec-=minutes*60;
        return [days,hours,minutes,sec];
    };

        // 引数を秒数に変換
    let sec = (hours===null) ? daysOrSeconds
        : seconds + minutes * 60 + hours * 3600 + daysOrSeconds * 86400;

        // スタートのタイムスタンプ取得
    const startTime = Date.now();
        // 初回の残り時間通知
    tickCallBack( calcTime(sec) );

        // タイマースタート(1秒間隔)
    const timer = setInterval(
        ()=>{
                // 残り時間を秒で計算
            let nowSec= sec - Math.floor( (Date.now() - startTime) / 1000 );
            if( nowSec <= 0 ) { // 残り時間なし
                clearInterval(timer); // タイマー停止
                endCallBack(); // 終了通知
            }else{
                tickCallBack( calcTime(nowSec) ); // 残り時間通知
            }
        },1000
    )

};

コードが長いですが、処理的には単純です。

先頭の二つの引数は残り時間と終了の通知を受け取るコールバック関数を指定します。

3つ目以降はカウントの開始時間を指定します。
このとき4つ目以降に引数がない場合は、3つ目の引数を秒として扱います。

4つ目以降に引数がある場合は、3つ目から順番に日数・時間数・分数・秒数が指定されたとします。

なお引数の妥当性はおこなっていません。
必要なら追加してください。

使用例

では実際に使用しています。
次のスタートボタンを押すとカウントダウンが始まり、残り時間がゼロになった時点で終了します。

デモ

----

デモで使用したHTMLコードがこれ。

HTML


<button id="timerstart">スタート</button>
<p id="timer"></p>

JavaScriptコードは次のようになっています。

JavaScript


window.addEventListener( "DOMContentLoaded" , ()=> {

    const button = document.getElementById("timerstart");
    const p = document.getElementById("timer");

       // 残り時間通知受け取り関数
    const tickFunc = ( time  )=>{
        p.textContent = `残り${ time[0] }${ time[1] }時間 ${ time[2] }${ time[3] }秒`;
    };
       // 終了通知受け取り関数
    const endFunc = ()=>{
        p.textContent = "終了しました";
    };

    button.addEventListener("click",()=>{
        coutdownTimer( tickFunc , endFunc , 70 );
    });
});

Promiseに置き換える

この程度のスクリプトは使用する意味がないのですが、同期処理はPromiseを使用すべきという方が一部でいるので、Promiseに置き換えてみます。


const coutdownTimer =  ( tickCallBack , endCallBack
    ,daysOrSeconds , hours = null , minutes  = null , seconds = null ) => {

        // 省略

    return new Promise( resolve => {
        // タイマースタート(1秒間隔)
        const timer = setInterval(
            ()=>{
                    // 残り時間を秒で計算
                let nowSec= sec - Math.floor( (Date.now() - startTime) / 1000 );
                if( nowSec <= 0 ) { // 残り時間なし
                    clearInterval(timer); // タイマー停止
                    if( endCallBack ) endCallBack(); // 終了通知
                    resolve();
                }else{
                    tickCallBack( calcTime(nowSec) ); // 残り時間通知
                }
            },1000
        )
    });
};

タイマー処理までは全く同じコードのため、ここでは省略してあります。
また省略した都合上、終了コールバックとPromiseの両方に対応したコードになっています。

このコードに対応したcoutdownTimer呼び出しは、次のようになります。


coutdownTimer( tickFunc , null , 70 )
            .then( ()=>endFunc() );

Promiseオブジェクトを経由することで余計な内部処理が増えます。
thenが使えること以外、あまり利点がないですね。

 

数値画像でカウントダウンする

0から9までの画像を用意して、その画像でカウントダウンをしてみます。

デモ

------

残り時間

HTMLは次のようになっています。

HTML


<button id="timerstart">スタート</button>
<p id="msg"></p>
<p id="timer">
    残り<img src="https://note.affi-sapo-sv.com/img/n0.png">
    <img src="https://note.affi-sapo-sv.com/img/n0.png">    <img src="https://note.affi-sapo-sv.com/img/n0.png">
    <img src="https://note.affi-sapo-sv.com/img/n0.png">時間
    <img src="https://note.affi-sapo-sv.com/img/n0.png">
    <img src="https://note.affi-sapo-sv.com/img/n0.png">    <img src="https://note.affi-sapo-sv.com/img/n0.png">
    <img src="https://note.affi-sapo-sv.com/img/n0.png"></p>

スクリプトコードはこちら。

JavaScript


const button = document.getElementById("timerstart");
const pmsg = document.getElementById("msg");
const ptimer = document.getElementById("timer");
const imgs = ptimer.querySelectorAll("img");

const srcimg = [
        "https://note.affi-sapo-sv.com/img/n0.png",
        "https://note.affi-sapo-sv.com/img/n1.png",
        "https://note.affi-sapo-sv.com/img/n2.png",
        "https://note.affi-sapo-sv.com/img/n3.png",
        "https://note.affi-sapo-sv.com/img/n4.png",
        "https://note.affi-sapo-sv.com/img/n5.png",
        "https://note.affi-sapo-sv.com/img/n6.png",
        "https://note.affi-sapo-sv.com/img/n7.png",
        "https://note.affi-sapo-sv.com/img/n8.png",
        "https://note.affi-sapo-sv.com/img/n9.png",
        ];
const tickFunc = ( time  )=>{
        imgs[0].src = srcimg[Math.floor(time[0] / 10)];
        imgs[1].src = srcimg[time[0] % 10];

        imgs[2].src = srcimg[Math.floor(time[1] / 10)];
        imgs[3].src = srcimg[time[1] % 10];

        imgs[4].src = srcimg[Math.floor(time[2] / 10)];
        imgs[5].src = srcimg[time[2] % 10];

        imgs[6].src = srcimg[Math.floor(time[3] / 10)];
        imgs[7].src = srcimg[time[3] % 10];
    };
const endFunc = ()=>{
        pmsg.textContent = "終了しました";
    };

button.addEventListener("click",()=>{
        pmsg.textContent = "開催中です";
        coutdownTimer( tickFunc , endFunc , 70 );
    });

カウントダウンの主要関数coutdownTimerはそのまま使用して、ブラウザの表示部分を変更します。

今回はあらかじめimgタグを用意しておき、src属性を変更しています。

数値画像がキャッシュされていない場合srcを変更した時点で読み込みがおこなわれるので、通信状況によっては表示が乱れる可能性があります。

気になる場合は、次のコードでサーバーから先読みをしておくといいです。

数値画像の先読み


for( let i = 0 ; i < srcimg.length ; i ++ )
    document.createElement( "img" ).src = srcimg[i];

 

気持ちを焦らせるカウントダウンタイマー

ここまでは秒単位のカウントタイマーを紹介してきました。
でもなんだか、のんびりしてる印象を受けますね。

次のデモを実行してみてください。

デモ


------

残り時間ミリ秒

最後のミリ秒が目まぐるしく変化するの見ていると、なんだか今すぐ行動しないていけないような気分になって、気持ちが焦ってきますね。

スクリプトコードは、これまで秒だったものをミリ秒にするだけです。
ご自由に改良してみてください。

更新日:2023/02/06

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

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

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

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

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

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

 

このサイトは、リンクフリーです。大歓迎です。