スピナー
stimeo--spinner
読み込み中の表示を出し入れします。始まりと終わりは読み上げにも伝えます。
読み込み中の表示を出したり消したりします。対象の領域が処理中であることも読み上げに伝わります。回っている絵は目のためのものなので、始まりと終わりは共通の窓口から言葉でも伝えます。表示が現れた瞬間の文字は確実には読まれないためです。ちらつきを抑える工夫を 2 つ持っています。すぐ終わる処理では、そもそも出しません。いったん出したら、見えたと分かる程度は残します。そのため、終わった直後に少しだけ残って消えますが、これは正しい動きです。回る絵はこのデモの CSS で描いています。
「読み込み開始」で読み込み中になり、「読み込み停止」で終わります。ちらつきを抑えるため、表示は少し遅れて現れ、いったん出ると短い間は残ります。停止の直後に少し残って消えるのは正しい動きです。
キーボード操作
このコンポーネント自体はキーボード操作を持ちません。
<%# Markup for the spinner (loading indicator) demo.
The indicator is the visual half — its text plus a spinner marked aria-hidden="true" —
and the controlled region reflects aria-busy. An indicator revealed at the moment of
the change is not reliably read, so the loading / ready wording goes to the shared
stimeo--announcer your app seats once, in its layout, and is said exactly once
(loading ↔ ready is the transition worth reading; aria-busy is state the reader can
query, not a notification). A show delay and a minimum display time suppress flicker.
The Start/Stop buttons call the controller's methods directly. %>
<div
class="spinner-demo"
data-controller="stimeo--spinner"
data-stimeo--spinner-delay-value="150"
data-stimeo--spinner-min-duration-value="600"
data-stimeo--spinner-announce-text-value="<%= t("components.spinner.demo.loading") %>"
data-stimeo--spinner-announce-ready-text-value="<%=
t("components.spinner.demo.announce_ready") %>">
<div class="spinner-demo__controls">
<button
class="demo-trigger"
type="button"
data-action="click->stimeo--spinner#start">
<%= t("components.spinner.demo.start") %>
</button>
<button
class="demo-trigger"
type="button"
data-action="click->stimeo--spinner#stop">
<%= t("components.spinner.demo.stop") %>
</button>
</div>
<p class="spinner-demo__hint"><%= t("components.spinner.demo.hint") %></p>
<%# Visual only: the announcer says this once, so a live region here would repeat it. %>
<div
class="spinner"
hidden
data-stimeo--spinner-target="indicator">
<span class="spinner__icon" aria-hidden="true"></span>
<span data-stimeo--spinner-target="message"><%= t("components.spinner.demo.loading") %></span>
</div>
<div
class="spinner-demo__region"
aria-busy="false"
data-stimeo--spinner-target="region">
<%= t("components.spinner.demo.content") %>
</div>
</div>
/*
* Presentation-only styles for the spinner demo.
* This CSS owns the visual spinner's rotation; the library only toggles
* hidden / aria-busy / data-state (idle / pending / loading).
*/
.spinner-demo {
display: flex;
flex-direction: column;
gap: 1rem;
max-width: 32rem;
}
.spinner-demo__controls {
display: flex;
gap: 0.5rem;
}
/* Explanatory caption: clarifies the intentional delay / minimum-display timing. */
.spinner-demo__hint {
margin: 0;
font-size: 0.85rem;
line-height: 1.5;
color: var(--color-text-muted);
}
.spinner {
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.95rem;
color: var(--fg);
}
.spinner__icon {
width: 1.1rem;
height: 1.1rem;
border: 2px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
animation: spinner-rotate 0.7s linear infinite;
}
@keyframes spinner-rotate {
to {
transform: rotate(360deg);
}
}
.spinner-demo__region {
min-height: 3rem;
padding: 0.75rem 1rem;
border: 1px dashed var(--border);
border-radius: 0.375rem;
color: var(--fg);
}
/* Dim the target region while loading so the visual matches aria-busy. */
.spinner-demo__region[aria-busy="true"] {
opacity: 0.5;
}
@media (prefers-reduced-motion: reduce) {
.spinner__icon {
animation: none;
}
}
このデモに固有の消費側 JS はありません(挙動はコントローラが担います)。
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--spinner"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
indicator
|
スピナーとして表示・非表示する、テキストを持つ視覚専用の要素。 | data-stimeo--spinner-target="indicator" |
region
|
aria-busy で読み込み状態を反映する被制御リージョン。 |
data-stimeo--spinner-target="region" |
message
|
インジケーター内の、読み込み中であることを視覚的に示すテキスト要素。 | data-stimeo--spinner-target="message" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
announceText
|
読み込みに入ったときに読み上げる文言。空なら告知しない。 | data-stimeo--spinner-announce-text-value |
announceReadyText
|
読み込みが終わったときに読み上げる文言。空なら告知しない。 | data-stimeo--spinner-announce-ready-text-value |
delay
|
開始後にスピナーを抑止するミリ秒。高速処理では表示しない(既定 0)。 | data-stimeo--spinner-delay-value |
minDuration
|
表示後のスピナーをちらつかせない最小表示ミリ秒(既定 0)。 | data-stimeo--spinner-min-duration-value |
timeout
|
data-stimeo--spinner-timeout-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
start
|
読み込みを開始し、busy を立て delay 後にスピナーを表示する。 | stimeo--spinner#start |
stop
|
読み込みを終了し、busy を下げ minDuration 後にスピナーを隠す。 |
stimeo--spinner#stop |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
hide
|
スピナーが隠れ idle に戻ったときに発火。 | stimeo--spinner:hide |
show
|
スピナーが表示されたときに発火。 | stimeo--spinner:show |
timeout
|
stimeo--spinner:timeout |
|
reconcile
|
Turbo キャッシュの巻き戻しが実行中のサイクルを idle へ戻したとき発火。detail は {}。 |
stimeo--spinner:reconcile |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
hidden |
indicator | 非表示中は付与し、表示時に外す。 |
aria-busy |
region | 読み込み中は "true"。 |
data-state |
ルート要素 | "idle" / "pending"(遅延待ち)/ "loading"。 |