ダウンロードの進捗を表示するとき、総バイト数が分からない応答に「0%」を出すと、通信が止まっているように見えます。受信量が分かることと、全体に対する割合が分かることは別です。今回は進捗イベントを表示用モデルへ変換し、不明な総量を明示する小さな関数を作ります。
私は Twitter Viewer を開発しています。公開動画を MP4 として保存する twitter video downloader のような UI を考える際にも、メディア情報取得とファイル転送の状態は分けて扱います。以下は一般的なサンプルで、サービス内部の実装ではありません。
総量が不明なら割合を返さない
XMLHttpRequest の進捗イベントには loaded、total、lengthComputable があります。計算可能でないイベントに仮の総量を入れて割合を作らず、不定状態を返します。fetch のストリームを自分で読む場合も、ヘッダーの値を総量として利用できるかは別に判断する必要があります。
export function progressModel(event) {
const loaded = Number.isFinite(event.loaded) && event.loaded >= 0
? event.loaded : 0;
const known = event.lengthComputable === true &&
Number.isFinite(event.total) && event.total > 0 &&
loaded <= event.total;
return {
loaded,
total: known ? event.total : null,
percent: known ? Math.floor(loaded / event.total * 100) : null,
label: known
? `${Math.floor(loaded / event.total * 100)}%`
: `${loaded} bytes received`
};
}
この関数はバイト数をそのまま表示します。実際の画面では KB や MB に整形しても構いませんが、単位の規則を統一します。総量より受信量が多い入力は、100% に丸めて隠さず「割合不明」へ戻す方針です。メタデータと実際の転送量が一致しない場合に、誤った精度を見せないためです。
境界値をネットワークなしで確かめる
関数を progress.mjs、以下を progress.test.mjs に保存します。
import test from 'node:test';
import assert from 'node:assert/strict';
import { progressModel } from './progress.mjs';
test('総量が分かる場合だけ割合を返す', () => {
assert.equal(progressModel({ loaded: 25, total: 100,
lengthComputable: true }).percent, 25);
assert.equal(progressModel({ loaded: 25, total: 0,
lengthComputable: false }).percent, null);
});
test('ゼロと不整合を分ける', () => {
assert.equal(progressModel({ loaded: 0, total: 100,
lengthComputable: true }).percent, 0);
assert.equal(progressModel({ loaded: 101, total: 100,
lengthComputable: true }).percent, null);
assert.equal(progressModel({ loaded: NaN }).loaded, 0);
});
node --test progress.test.mjs で実行できます。総量不明の応答を実際に用意しなくても、表示の判断を確認できます。このテストは転送速度やサーバーの挙動を検証するものではありません。
progress要素にも不定状態がある
HTML の <progress> は value 属性がない場合に不定状態を表せます。総量が分からない間は value="0" を固定する代わりに、属性を外す方法があります。周囲の文章で「受信中、総量不明」と説明し、受信済みの量を補助情報として出します。
割合が分かるようになったら max="100" と計算した value を設定します。スクリーンリーダー向けに毎バイト更新を読み上げる必要はありません。短い間隔の変更を集約し、開始、失敗、完了のような意味のある変化を伝えます。
100%と保存完了を同一視しない
この割合は、扱っている転送の受信量に関する情報です。受信後に変換や検証があるなら、それらの処理は別状態です。また、通常のリンクでブラウザーのダウンロード管理へ渡した場合、ページ側が最終保存まで観測できるとは限りません。
表示には「受信完了」「変換中」「ブラウザーで保存先を確認」のように確認できた事実を使います。ユーザーが実際に保存したと確認できていない段階で、「端末への保存完了」を表示しないことが、このモデルの使い方の境界です。