作りたいもの
以下のようなものを作ります。
秒数のカウントダウンと連動してプログレスバーの長さ、カラーを変更させてみる。
仕様
- 秒数表示は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();