常に最新の進捗バーだけを表示させる
Javascript を使わずに、サーバーからプッシュ型で進捗バーをブラウザに送信します。(この記事では、サーバー側のスクリプト言語は PHP を使っていますが、他の言語でも同様にできるはずです。)
下記のように、進捗バーが更新されていき、最新の進捗バーだけが表示されます。(画像は、Windows10, Firefox)
説明
PHP に限らず、時間のかかる大量のデータ処理を行う場合、進捗バーが表示できれば便利です。
最初、HTML5 の progress 要素を利用することを思いつきましたが、最新の進捗バーを自動更新する方法がなかなか見つかりませんでした。ネット検索では、iframe 要素とか、Javascript とか、面倒そうなものばかり。
「■」を表示させる方法が見つかりましたが、総数が大きな素数とか、又はとても小さな数の場合、進捗を表す「■」をどのタイミングでいくつ追加するかがとても煩雑です。
ところが、CSS3 を使えば、古い進捗バーを {display:none} で消してしまうことができます。この方法で、(過去の余分な progress 要素が残ってしまいますが、)
最新の進捗バーだけをリアルタイム表示させることができます。
<?php
# データ処理すべき総数(ここでは 100 とします。)
$total = 100;
# 処理が済んだ件数は、$i とします。このコードでは下の for ループで 1 ずつカウントアップされます。
# ブラウザに style 要素と p 要素を送信する
echo <<<HTML
<style>
p progress:not(:last-of-type) {display:none}
</style>
<p>
HTML;
for ( $i = 1; $i <= $total; $i++ ) {
if ($i % ($total / 5) == 0) { // 20 %ごとに進捗バーを更新する
sleep(1);
# ブラウザに progress 要素を送信する(親要素である p 要素に progress 要素を追加していく)
echo '<progress value="'.$i.'" max="'.$total.'">'.($i / $total * 100).'%</progress>';
if ($i===$total){ // 100% なら半角空白100% を追加
echo ' 100%';
}
ob_flush();
flush();
}
}
このコードでは、整数をカウントアップしていき、20 カウント(20%)ごとに 1 秒待って進捗バーを表示させます。
なお、古い進捗バー(progress 要素)は、最初に echo された style 要素の設定で非表示になり、常に最新の進捗バーだけが表示されることによって、あたかも自動更新されているように見えます。
(実際には古い進捗バーは、非表示にされていますが、HTML ソース上は下記のように残っています。)
<style>p progress:not(:last-of-type) {display:none}</style>
<p><progress value="20" max="100">20%</progress><progress value="40" max="100">40%</progress><progress value="60" max="100">60%</progress><progress value="80" max="100">80%</progress><progress value="100" max="100">100%</progress> 100%
このコードの「ミソ」は、HTML5 の仕様「p 要素の終了タグは省略できる!」を利用して、p 要素の終了タグ(</p>
)を書かないまま、progress 要素をどんどん p 要素に追加していくところです。ブラウザが自動的に p 要素の終了タグ(</p>
)を補完して表示してくれます。
そして、style 要素で最新の progress 要素だけを表示させます。
<style>
p progress:not(:last-of-type) {display:none}
</style>
この方法を使えば、例えば進捗バーでなくて「文字列」で「○○%」と表示させることもできます。この場合、progress 要素の代わりに、span 要素や i 要素、u 要素などを使います。
<style>
p span:not(:last-of-type) {display:none}
</style>
echo '<span>'.floor($i / $total * 100).'%</span>';
また、両者を組み合わせれば、進捗バーの後に続けて文字列を表示させることもできます。
echo <<<HTML
<style>
p progress:not(:last-of-type),
p span:not(:last-of-type) {display:none}
</style>
<p>
HTML;
# 下記は、ループ処理の中などで繰り返し実行する
echo '<progress value="'.$num_of_files.'" max="'.$total_files.'">'.($num_of_files / $total_files * 100).'%</progress><span> '.(time() - $time0).'秒: '.$num_of_files.' ファイル('.number_format($total_file_size).'バイト)</span>';
ob_flush();
flush();
ちなみに、progress 要素の開始タグと終了タグに挟まれた部分は、進捗バーを表示できない環境で表示されるそうです。