ファイルのアップロードやメール送信などで見かける、進行状況を表現するプログレスバーは簡単な仕組みで構成されています。
もちろんライブラリを使った仕様も良いのですが、仕組みを知っておけばライブラリのカスタマイズにも生かせるはずなので解説してみます。
また動画もあるので必要に応じて活用してください。
プログレスバーをHTMLで作る方法
まずプログレスバーの見た目ですがHTMLで専用のタグでprogressというものを使うと簡単に作ることができます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./style.css" />
<script src="./script.js" defer></script>
</head>
<body>
<div>
<label for="">
<progress></progress>
</label>
</div>
</body>
</html>
progressタグは名前の通りプログレスバーをブラウザに表示するためのタグです。
progressには覚えておく属性が2種類あります。
①max : 最大値
②value : 現時点の値
それぞれ以下のように追加してみます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./style.css" />
<script src="./script.js" defer></script>
</head>
<body>
<div>
<label for="">
<progress max="100" value="80" id="progress"></progress>
</label>
</div>
</body>
</html>
上記のコードでは「最大値を100として現時点の値を80とする」ということになるのでプログレスバーのゲージが変わりました。
ちなみにmax属性については省略すると自動的にvalue属性の値が「0〜1の間」になるので注意してください。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./style.css" />
<script src="./script.js" defer></script>
</head>
<body>
<div>
<label for="">
<!-- maxを省略するとvalueは0〜1の間で指定することになる -->
<progress value="0.8" id="progress"></progress>
</label>
</div>
</body>
</html>
progressタグをJavaScriptで操作する方法
progressのvalue属性の値はJavaScriptで指定することもできます。
HTMLは以下のようにします、value属性は一旦0にしておきます。
また取得した値を画面に数字として表示したいのでpタグを追加しました。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./style.css" />
<script src="./script.js" defer></script>
</head>
<body>
<div>
<label for="">
<!-- valueを0に変更 -->
<progress max="100" value="0" id="progress"></progress>
</label>
<!-- pタグを追加 -->
<p id="progressResult"></p>
</div>
</body>
</html>
JavaScript側ではinputタグ同様に要素を取得すると、value属性にアクセスするための「value」という同じ名前のプロパティが用意されています。
const progress = document.querySelector("#progress");
const progressResult = document.querySelector("#progressResult");
// value属性にアクセスできる
progress.value = 80;
HTMLのvalue属性で指定した時と同じ見た目にすることができましたね。
さらに上記のコードではpタグを追加して値を数字として表示することを想定していたので、JavaScriptのinnerHTMLを使って先ほどの値を挿入してみます。
const progress = document.querySelector("#progress");
const progressResult = document.querySelector("#progressResult");
progress.value = 80;
// ここを追記
progressResult.innerHTML = progress.value;
valueに追加した値を画面でも数字として確認できましたね。
これらの作業を踏まえて、値が0〜100に増えていくアニメーションを作って見たいと思います。
JavaScriptを以下のように作り替えます。
const progress = document.querySelector("#progress");
const progressResult = document.querySelector("#progressResult");
// 進捗の値
let percentage;
// 進捗状況の数字を増やす関数
function increment() {
percentage += 1;
// 進捗に+1することをprogressのvalueにも入れる
progress.value = percentage;
progressResult.innerHTML = progress.value;
}
// ボタンを押したときに発動する関数
function animation() {
// 進捗状況の値をprogressのvalueの値に上書きする(初期値はHTML側でvalue=0としてある)
percentage = progress.value;
setInterval("increment()", 100);
}
今回は関数を2種類用意しています。
①increment : valueの値を+1する関数
②animation : increment関数を0.1秒ごとに繰り返す関数
役割を分ける形にしています。
①incrementはprogressのvalueに+1して、追加した後のvalueの値を画面に表示します。
これだけだと「0→1」にしかならないので、②animationで100になるまで繰り返します。
繰り返すための関数はsetIntervalというもので、第一引数に繰り返す関数名、第二引数に繰り返す間隔を指定します。
現状だと関数を用意しただけなので、HTML側でボタンを作って「ボタンを押すと実行する」ようにしてみましょう。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./style.css" />
<script src="./script.js" defer></script>
</head>
<body>
<div>
<label for="">
<progress id="progress" max="100" value="0"></progress>
</label>
<p id="progressResult"></p>
<!-- ここを追加 -->
<div>
<button id="btn" onclick="animation()">クリック</button>
</div>
</div>
</body>
</html>
ボタンを追加しました、buttonタグにはonclick属性がありJavaScriptで作った関数名を()を付けて書くと「ボタンを押したら関数を実行」という意味になります。
先ほどJavaScript側で2種類の関数を作りました。
①increment : valueの値を+1する関数
②animation : increment関数を0.1秒ごとに繰り返す関数
とはいえ①incrementは②animationの中に含まれる関数なので、ボタンを押した時に実行するのは②animationだけで大丈夫です。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./style.css" />
<script src="./script.js" defer></script>
</head>
<body>
<div>
<label for="">
<progress id="progress" max="100" value="0"></progress>
</label>
<p id="progressResult"></p>
<div>
<!-- ボタンを押したらanimation関数が実行される -->
<button id="btn" onclick="animation()">クリック</button>
</div>
</div>
</body>
</html>
ブラウザをリロードしてボタンを押してみます。
一定間隔で数字が増えているのとプログレスバーのゲージも進行していますね。
これで大丈夫そうですが一点だけ問題があります。
先ほどのJavaScriptにてvalueの値をコンソールで出力するようにしてみましょう。
const progress = document.querySelector("#progress");
const progressResult = document.querySelector("#progressResult");
let percentage;
function increment() {
percentage += 1;
progress.value = percentage;
progressResult.innerHTML = progress.value;
}
function animation() {
percentage = progress.value;
setInterval("increment()", 100);
// ここを追加
console.log(progress.value);
}
ブラウザをリロードして検証ツールでコンソールを開きながら実行してみます。
画面上は問題ないのですが、コンソールで見ると100に到達してから止まらずに100だけを繰り返し出力してしまっています。
いわゆる無限ループのような状態です。
こちらJavaScript側に原因がありますので確認してみます。
const progress = document.querySelector("#progress");
const progressResult = document.querySelector("#progressResult");
let percentage;
function increment() {
percentage += 1;
progress.value = percentage;
progressResult.innerHTML = progress.value;
}
function animation() {
percentage = progress.value;
setInterval("increment()", 100);
console.log(progress.value);
}
animation関数ではincrement関数を0.1秒ごとに繰り返します。
HTML側のprogressのmax属性を100にしたので100でバーはいっぱいになるのですが、animationの中の世界では「繰り返す回数」を設定していないので100を繰り返し続けることになっていたわけです。
こちら改善する方法としては「繰り返しを止める条件」を追加するのが良さそうです。
JavaScriptを以下のように修正します。
const progress = document.querySelector("#progress");
const progressResult = document.querySelector("#progressResult");
let percentage;
// ここを追加
let interval;
function increment() {
percentage += 1;
progress.value = percentage;
progressResult.innerHTML = progress.value;
// ここを追加
if (percentage === 100) {
clearInterval(interval);
}
console.log(progress.value);
}
function animation() {
percentage = progress.value;
// ここを修正
interval = setInterval("increment()", 100);
}
increment関数の中で条件式を追加して、条件の内容は「数字が100になったら」というものです。
条件に達した時に実行する関数がclearIntervalというもので、「setIntervalで動いた繰り返し処理を停止」することを行なってくれます。
clearIntervalの引数にsetIntervalを変数intervalにして設定しました。
ブラウザをリロードして再度動かしてみます。
画面の表示と同じくコンソールの数字も100で止まりましたね。
本題と少し外れましたが、画面上での動きと裏側の動きは必ずしも同じになるとは限りません。
JavaScriptを使ったアニメーションの実装時に今回のようなことを意識するとスキルが向上するかと思います。
また今回の記事を作成するにあたり以下の本を参考にしていますので必要な方はどうぞ