ローディングアニメーションを実装する🌀
Rails + Turbo + Stimulus 環境で
ページ遷移やフォーム送信時に自動表示されるローディングを実装します。
この手順通りに進めれば、他プロジェクトでもそのまま再現可能です。
0. ローディングの仕組みを理解する
ローディングは次の 2つの要素 で構成されます。
| 役割 | 内容 |
|---|---|
| (1) DOM(application.html.erb) | ローディング画面の見た目(overlay) |
| (2) Stimulus Controller | Turbo のイベントを監視して表示 / 非表示を制御 |
DOM を置くだけでは動きません。
Turbo のイベントを Stimulus が捕まえることで自動表示されます。
1. レイアウトにローディング用 DOM を置く
対象ファイル:
app/views/layouts/application.html.erb
</body> の直前に追加します。
<!-- ローディングオーバーレイ -->
<div
data-controller="loading"
data-loading-target="overlay"
class="fixed inset-0 z-[9999] flex flex-col items-center justify-center
bg-white/70 backdrop-blur-sm hidden">
<!-- ドット3つのアニメーション -->
<div class="flex space-x-2 mb-3">
<div class="w-3 h-3 bg-orange-500 rounded-full animate-bounce"></div>
<div class="w-3 h-3 bg-orange-400 rounded-full animate-bounce [animation-delay:0.15s]"></div>
<div class="w-3 h-3 bg-orange-300 rounded-full animate-bounce [animation-delay:0.3s]"></div>
</div>
<p class="text-sm text-gray-700 font-medium">
読み込み中です…
</p>
</div>
ポイント
2. Stimulus コントローラで表示 / 非表示を制御する
対象ファイル:
app/javascript/controllers/loading_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["overlay"]
connect() {
this.show = this.show.bind(this)
this.hide = this.hide.bind(this)
this.delayTimer = null
this.DELAY = 600 // おすすめ:500〜700ms(チラつき防止)
// Turbo イベントを監視
document.addEventListener("turbo:before-visit", this.show)
document.addEventListener("turbo:submit-start", this.show)
document.addEventListener("turbo:load", this.hide)
document.addEventListener("turbo:submit-end", this.hide)
}
disconnect() {
document.removeEventListener("turbo:before-visit", this.show)
document.removeEventListener("turbo:submit-start", this.show)
document.removeEventListener("turbo:load", this.hide)
document.removeEventListener("turbo:submit-end", this.hide)
}
show() {
if (!this.hasOverlayTarget) return
// 既存タイマーがあればクリア(イベント多重発火対策)
if (this.delayTimer) {
clearTimeout(this.delayTimer)
}
// 一定時間後に表示
this.delayTimer = setTimeout(() => {
this.overlayTarget.classList.remove("hidden")
}, this.DELAY)
}
hide() {
if (!this.hasOverlayTarget) return
// 表示前ならキャンセル
if (this.delayTimer) {
clearTimeout(this.delayTimer)
this.delayTimer = null
}
this.overlayTarget.classList.add("hidden")
}
}
なぜ遅延を入れるのか?
-
ページ遷移が高速な場合 → ローディングが一瞬だけ表示されてチラつく
-
500〜700ms 遅らせる → 「待つときだけ出る」自然な UX になる
3. Turbo が発火するイベント一覧
Stimulus でキャッチしているイベントは以下の 4 つです。
| イベント名 | 発火タイミング | ローディング挙動 |
|---|---|---|
| turbo:before-visit | 別ページへ遷移する直前 | show |
| turbo:submit-start | フォーム送信開始 | show |
| turbo:load | 画面描画完了(初回含む) | hide |
| turbo:submit-end | フォーム送信完了 | hide |
※ turbo:load は 初回ロード時にも必ず発火 するため、
「保険的に hide する用途」として使うのが定番です。
4. Stimulus の読み込み設定
Rails(stimulus-rails)標準構成では、
*_controller.js を配置
controllers/index.js で登録
という形で読み込まれます。
// app/javascript/controllers/index.js
import { application } from "./application"
import LoadingController from "./loading_controller"
application.register("loading", LoadingController)
5. 実装チェックリスト
次の 5 点が揃っていればローディングは動きます。
✔ application.html.erb に overlay がある
✔ data-controller="loading" が付いている
✔ loading_controller.js が正しい場所にある
✔ Stimulus が読み込まれている
✔ Turbo が有効(リンク・フォームが Turbo)
6. よくあるエラーと対処法
| 症状 | 原因 | 対処 |
|---|---|---|
| ローディングが出ない | Controller が読み込まれていない | index.js を確認 |
| ずっと表示される | hide が発火していない | turbo:load を確認 |
| 一瞬しか表示されない | 遷移が高速すぎる | 遅延時間を増やす |
| モーダル内だけ使いたい | layout に置いている | 専用 layout に移動 |
7. 応用できるように理解しておくポイント
オーバーレイはレイアウトに置く
→ 全ページ共通なら application.html.erb
Turbo イベントが制御の起点
→ Stimulus は「いつ表示するか」を決める役割
Tailwind だけで表現可能
→ bounce / pulse / spin などに簡単に変更できる