Help us understand the problem. What is going on with this article?

Bootstrap のプログレスバーをカウントダウンタイマーとして使ってみる

More than 3 years have passed since last update.

作りたいもの

以下のようなものを作ります。
秒数のカウントダウンと連動してプログレスバーの長さ、カラーを変更させてみる。

スクリーンショット 2016-10-10 17.18.57.png

仕様

  • 秒数表示は1秒毎に更新
  • 秒数に応じてバーの長さも小さくなる
  • 長さに応じてバーの色を変える
    • 始めは .progress-bar-info
    • 半分まで過ぎたら .progress-bar-warning
    • 5分の1まで過ぎたら .progress-bar-danger

作ってみた

ここ にとりあえず動くものを置いてます。

jQuery のプラグインとして作ったので、次のようにすれば <div id="hoge"> 以下に要素を生成してカウントダウンを開始します。

呼び出し部分
$('#hoge').timer(10);
本体
 (function($) {
  $.fn.timer = function(totalTime) {
    // 既に起動済のものがある場合は削除しておく
    clearTimeout(this.data('id_of_settimeout'));
    this.empty();

    // ターゲット内に要素を作成
    this.append('<h4><span></span> seconds left.</h4>');
    this.append('<div class="progress"></div>');
    this.children('.progress').append('<div class="progress-bar progress-bar-info"></div>');
    this.find('.progress-bar').css({
        cssText: '-webkit-transition: none !important; transition: none !important;',
        width: '100%'
    });

    var countdown = (function(timeLeft) {
      var $progressBar = this.find('div.progress-bar');
      var $header = this.children('h4');

      if (timeLeft <= 0) {
        $header.empty().text('Over the time limit!').addClass('text-danger');
        return;
      }

      $header.children('span').text(timeLeft);

      var width = (timeLeft - 1) * (100/totalTime); // unit in '%'
      if (width < 20) { // less than 20 %
        $progressBar.removeClass();
        $progressBar.addClass('progress-bar progress-bar-danger');
      } else if (width < 50) { // less than 50 % (and more than 20 %)
        $progressBar.removeClass();
        $progressBar.addClass('progress-bar progress-bar-warning');
      }

      $progressBar.animate({
        width:  width + '%'
      }, 1000, 'linear');

      var id = setTimeout((function() {
        countdown(timeLeft - 1);
      }), 1000);
      this.data("id_of_settimeout", id);
    }).bind(this);

    countdown(totalTime);
  };
})(jQuery);

詳細

ハマったポイント等書いておきます。

JavaScript での一定間隔で繰り返し処理の基本形

関数の最後で同じ関数を setTimeout を使って時間差で呼び出します。

var time = 10;
var countdown = function() {
  console.log(time);

  if (time === 0) {
    return;
  }

  time--;

  setTimeout(countdown, 1000); // 第二引数に指定した時間(ミリ秒)経ってから countdown を実行
}

//呼び出し
countdown();

グローバル変数を使わない形に

time のスコープが関数の外にあると気持ち悪いので修正。
引数に時間を渡して setTimeout にはデクリメントした値を引数に渡すように。

var countdown = function(time) {
  console.log(time);

  if (time === 0) {
    return;
  }

  // setTimeout で実行する関数に直接引数を渡すことはできないらしいので
  // 無名関数とし、その中で time - 1 を渡す
  setTimeout(function() {countdown(time - 1)}, 1000);
}

// 呼び出し
countdown(10);

プログレスバーの長さを変える

タイマー部分のHTML
<div id="timer">
  <h4><span>10</span> seconds left.</h4>
  <div class="progress">
    <div class="progress-bar progress-bar-info" style="width: 100%;"></div>
  </div>
</div>
基本形
var totalTime = 10;

var countdown = function(timeLeft) {
  if (timeLeft <= 0) {
    // 終了時の処理
    return;
  }

  // 残り時間の表示
  $('#timer > h4 span').text(timeLeft);

  // バーの長さ(width)を変更
  var width = (timeLeft - 1) * (100/totalTime);
  $('#timer div.progress-bar').animate({
    width:  width + '%'
  }, 1000, 'linear'); // 1秒間掛けて線形にアニメーション

  setTimeout((function() {countdown(timeLeft - 1)}), 1000);
}

時間表示とバー幅の計算で1秒分差があるのがポイントですね。

ローカルに定義した関数内での this の扱い

windowオブジェクトを指す
(function($) {
  $.fn.hoge = function() {
    console.log(this); // A

    var innerFunc = function() {
      console.log(this); // B ここで $('div') を使いたいけれどこの this は window を参照してしまう
    }

    innerFunc();
  }
})(jQuery);

$('div').hoge();

このように呼び出すと A, B それぞれの出力は「呼び出し元の jQuery オブジェクト」、「window オブジェクト」になる。
内部で定義した関数内でも元の jQuery オブジェクトを使いたいが、わざわざ引数に渡すのもスマートでないので、次のようにしてやると関数内での this の指すオブジェクトを固定できます。

jQueryオブジェクトを指すように変更
(function($) {
  $.fn.hoge = function() {
    console.log(this); // A
    var innerFunc = function() {
      console.log(this); // B
    }.bind(this); // ここでの this は呼び出し元の jQuery オブジェクト。これを関連付ける

    innerFunc();
  }
})(jQuery);

$('div').hoge();
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした