ローディング表示を一言でいうと
ローディング表示は、
「今、アプリはちゃんと動いているよ。止まっているんじゃなくて“待っているだけ”だよ」
とユーザーに伝えるためのサインです。
非同期処理(fetch / API 通信)は、
「ボタンを押してから結果が返るまでに“間”がある」のが当たり前です。
その“間”を何も表示しないと、ユーザーはこう感じます。
「押せてない?」「フリーズした?」「もう一回押した方がいい?」
ローディング表示は、
この不安を消すための“心のインターフェース”でもあります。
非同期処理とローディング表示の基本的な関係
「処理中」と「処理完了」の境目をコードで持つ
ローディング表示をちゃんと扱うには、
まず「今は処理中かどうか」をコード上で表現する必要があります。
一番シンプルなのは、
「処理開始時に true、終わったら false にするフラグ」です。
let isLoading = false;
async function loadUsers() {
isLoading = true; // 処理開始
try {
const res = await fetch("/api/users");
const data = await res.json();
renderUsers(data);
} finally {
isLoading = false; // 成功でも失敗でも必ず処理終了
}
}
JavaScriptこの isLoading を、
画面の表示と結びつけます。
function renderLoading() {
const el = document.getElementById("loading");
el.style.display = isLoading ? "block" : "none";
}
JavaScriptそして、状態が変わるたびに renderLoading() を呼ぶイメージです。
ここが重要です。
ローディング表示は「勝手に出たり消えたりするもの」ではなく、
“処理中かどうか”という状態をコードで管理し、それを UI に反映する設計 だと捉えると、
一気に筋が通ります。
finally で「必ず消す」を保証する
非同期処理では、
エラーが起きてもローディング表示が消えない、という事故がよく起きます。
例えば、こういうコードは危険です。
async function loadUsers() {
showLoading();
const res = await fetch("/api/users");
const data = await res.json();
hideLoading(); // ここに来る前にエラーが起きたら、ローディングが消えない
}
JavaScriptfetch や res.json() でエラーが起きたら、hideLoading() まで到達しません。
これを防ぐために使うのが try ... finally です。
async function loadUsers() {
showLoading();
try {
const res = await fetch("/api/users");
const data = await res.json();
renderUsers(data);
} catch (err) {
console.error(err);
showErrorMessage("ユーザーの取得に失敗しました");
} finally {
hideLoading(); // 成功でも失敗でも必ず実行される
}
}
JavaScriptここがとても重要です。
「ローディングを出す」と「ローディングを消す」は、
必ずセットで書く。
そして“どんな結果でも必ず消す”ために finally を使う。
このパターンを体に染み込ませておくと、
「ぐるぐるが永遠に回り続けるバグ」をかなり防げます。
実務でよくあるローディング表示のパターン
パターン1:画面全体を覆うローディング(フルスクリーン)
ページ全体のデータを読み込むときなどに使うパターンです。
HTML 例:
<div id="page-loading" class="loading-backdrop" style="display: none;">
<div class="spinner">Loading...</div>
</div>
JavaScript 例:
function showPageLoading() {
document.getElementById("page-loading").style.display = "flex";
}
function hidePageLoading() {
document.getElementById("page-loading").style.display = "none";
}
async function initPage() {
showPageLoading();
try {
const res = await fetch("/api/page-data");
const data = await res.json();
renderPage(data);
} catch (err) {
console.error(err);
showErrorMessage("ページの読み込みに失敗しました");
} finally {
hidePageLoading();
}
}
JavaScriptこれは、
「このページは今“読み込み中”で、まだ操作できません」
という状態をはっきり示したいときに使います。
パターン2:ボタン単位のローディング(スピナー+無効化)
フォーム送信やボタン押下時によく使うパターンです。
HTML 例:
<button id="save-button">
<span class="label">保存</span>
<span class="spinner" style="display: none;">...</span>
</button>
JavaScript 例:
const saveButton = document.getElementById("save-button");
function setSaveButtonLoading(isLoading) {
saveButton.disabled = isLoading;
saveButton.querySelector(".label").style.display = isLoading ? "none" : "inline";
saveButton.querySelector(".spinner").style.display = isLoading ? "inline" : "none";
}
async function onClickSave() {
setSaveButtonLoading(true);
try {
await saveForm();
showSuccessMessage("保存しました");
} catch (err) {
console.error(err);
showErrorMessage("保存に失敗しました");
} finally {
setSaveButtonLoading(false);
}
}
saveButton.addEventListener("click", onClickSave);
JavaScriptここでのポイントは、
ボタンを無効化して「連打」を防ぐ
ラベルを隠してスピナーを出すことで「今このボタンが処理中」と分かる
という 2 点です。
ここが重要です。
「ローディング表示」と「操作の無効化」はセットで考えると、
ユーザーの誤操作や二重送信をかなり防げます。
パターン3:部分的なローディング(リストだけ、カードだけ)
ページ全体ではなく、
「このリストだけ読み込み中」「このカードだけ更新中」
という局所的なローディングもよくあります。
例えば、ユーザー一覧だけにローディングを出す場合。
<div id="user-list">
<div id="user-list-loading" style="display: none;">読み込み中...</div>
<ul id="user-list-items"></ul>
</div>
function setUserListLoading(isLoading) {
document.getElementById("user-list-loading").style.display = isLoading ? "block" : "none";
}
async function loadUsers() {
setUserListLoading(true);
try {
const res = await fetch("/api/users");
const data = await res.json();
renderUserList(data);
} catch (err) {
console.error(err);
showErrorMessage("ユーザー一覧の取得に失敗しました");
} finally {
setUserListLoading(false);
}
}
JavaScriptこうすると、
ページ全体はそのまま操作できるけれど、
「ユーザー一覧のところだけ“今読み込み中”」という状態を表現できます。
「いつローディングを出すか」「どれくらい出すか」を考える
短すぎる処理にローディングを出しすぎない
実務では、
「何でもかんでもローディングを出せばいい」というわけではありません。
処理が 100ms くらいで終わるのに、
毎回スピナーがチカチカ出たり消えたりすると、
逆にうるさく感じます。
そこでよくやるのが、
「一定時間以上かかりそうならローディングを出す」という工夫です。
let loadingTimer = null;
function showLoadingWithDelay(delay = 200) {
loadingTimer = setTimeout(() => {
showLoading();
}, delay);
}
function hideLoadingWithDelay() {
clearTimeout(loadingTimer);
hideLoading();
}
async function loadSomething() {
showLoadingWithDelay(200); // 200ms 以上かかったらローディング表示
try {
const res = await fetch("/api/data");
const data = await res.json();
render(data);
} finally {
hideLoadingWithDelay();
}
}
JavaScriptこうすると、
「一瞬で終わる処理にはローディングを出さない」
「少し時間がかかるときだけ出す」
というバランスが取れます。
ここが重要です。
ローディング表示は「多ければ多いほど良い」ではなく、
“ユーザーが不安になる時間帯” にだけ出すのが理想です。
そのために、遅延表示というテクニックがよく使われます。
ローディング中に「何が起きているか」を伝える
単に「ぐるぐる」だけ出すより、
「何をしているのか」を一言添えると、
ユーザーの安心感がかなり変わります。
例えば、
「ユーザー情報を読み込んでいます…」
「保存しています…」
「検索結果を取得しています…」
などです。
コード的には、
ローディング用の要素にメッセージを差し込むだけです。
function showLoading(message = "読み込み中です…") {
const el = document.getElementById("loading");
el.textContent = message;
el.style.display = "block";
}
JavaScript呼び出し側でこう使えます。
showLoading("ユーザー情報を読み込んでいます…");
JavaScript複数の非同期処理が同時に走るときのローディング制御
「どれか一つでも処理中ならローディングを出したい」問題
実務では、
同じ画面で複数の API を同時に叩くことがよくあります。
例えば、
ユーザー情報を取得
通知一覧を取得
おすすめ情報を取得
など。
このとき、
単純にそれぞれで showLoading() / hideLoading() を呼ぶと、
先に終わった処理が hideLoading() を呼んでしまい、
まだ他の処理が動いているのにローディングが消えてしまう、
という問題が起きます。
これを防ぐために、
「今何件の処理が動いているか」をカウントする方法があります。
let loadingCount = 0;
function showGlobalLoading() {
loadingCount++;
document.getElementById("global-loading").style.display = "block";
}
function hideGlobalLoading() {
loadingCount = Math.max(loadingCount - 1, 0);
if (loadingCount === 0) {
document.getElementById("global-loading").style.display = "none";
}
}
JavaScript使い方はこうです。
async function loadUser() {
showGlobalLoading();
try {
const res = await fetch("/api/user");
const data = await res.json();
renderUser(data);
} finally {
hideGlobalLoading();
}
}
async function loadNotifications() {
showGlobalLoading();
try {
const res = await fetch("/api/notifications");
const data = await res.json();
renderNotifications(data);
} finally {
hideGlobalLoading();
}
}
JavaScriptこうすると、
最初の処理開始 → loadingCount = 1 → ローディング表示
二つ目の処理開始 → loadingCount = 2 → ローディング継続
一つ目が終わる → loadingCount = 1 → まだ表示
二つ目が終わる → loadingCount = 0 → ローディング非表示
という流れになります。
ここが重要です。
複数の非同期処理が同時に走る場面では、
「ローディングの ON/OFF を単純なフラグではなく“カウント”で管理する」
という発想がとても役に立ちます。
初心者として「ローディング表示」で本当に押さえてほしいこと
ローディング表示は、
「処理が遅いから仕方なく出すもの」ではなく、
「ユーザーの不安を減らすためのコミュニケーション」 だと捉える。
コード上では、
「処理中かどうか」を状態として持ち、
その状態を UI に反映する、という設計にする。
showLoading() と hideLoading() は必ずセットで書き、
どんな結果でも必ず消すために try ... finally を使う。
ボタン単位・部分単位・画面全体など、
「どの範囲をローディング状態にするか」を意識して選ぶ。
複数の非同期処理が同時に走る場合は、
「今何件処理中か」をカウントしてローディングを制御する、
というパターンを覚えておく。
そして何より、
「この待ち時間、ユーザーは何を感じているだろう?」
「何が見えていたら安心できるだろう?」
という視点でローディング表示を設計すると、
ただのスピナーが「気遣いのある UI」に変わっていきます。
今書いている非同期処理の中で、
「ここ、ユーザーから見ると“無言の待ち時間”になってない?」と感じる場所があったら、
そこに小さなローディング表示を一つ置いてみてください。
それだけで、アプリの“優しさ”が一段変わります。

