staggered-word-reveal について
テキストがビューポートに入ると単語ごとに表示する、フロントエンド向けのデザインスキルです。アクセシビリティとモーション軽減に配慮した、一度だけ再生する控えめなアニメーションを案内します。
主な機能
- 01単語ごとのテキスト分割と表示
- 02ビューポート進入時に一度だけ起動
- 03CSSとJavaScriptでの実装
- 04スクリーンリーダーとモーション軽減への配慮
活用シーン
- 01スクロールに合わせた短い見出しの表示
- 02引用文の段階的な表示
- 03ラベルへの控えめな動きの追加
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mengto/ skills --skill staggered-word-revealインストール後、Claude Code で/staggered-word-revealを入力して読み込まれたか確認します。
同梱ファイルdemo/index.htmldemo/PROMPT.md
詳細
単語ごとの段階的な表示
使用する場面
- 短い見出し、導入文、または引用文を単語ごとに表示したい場合。
- 動きに編集的で上質な、控えめな印象を持たせたい場合。
- テキストがビューポートに入ったときに、アニメーションを一度だけ開始したい場合。
- プロジェクトで GSAP SplitText の重い動作が必要ない場合。
モーションのデフォルト設定
- 初期状態:
opacity: 0、transform: translateY(20px)。 - 最終状態:
opacity: 1、transform: translateY(0)。 - 時間:
0.8s。 - イージング:
cubic-bezier(0.16, 1, 0.3, 1)。 - スタッガー:単語ごとに
0.06s〜0.08s。デフォルトは0.07sです。 - トリガー:ビューポートに約
20%表示された時点で開始し、やや下方に寄せます。 - 再生:一度のみ。
HTML
<h1 class="word-reveal" data-word-reveal>
Build interfaces that feel calm, cinematic, and alive.
</h1>
CSS
JavaScript が無効でもコンテンツを表示したままにします。JavaScript が有効になった後、テキストを分割する前にのみ非表示にします。
.word-reveal {
visibility: visible;
}
html.js .word-reveal[data-word-reveal]:not(.is-ready) {
opacity: 0;
}
.word-reveal__word {
display: inline-block;
opacity: 0;
transform: translate3d(0, 20px, 0);
transition:
opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
transition-delay: calc(var(--word-index) * 0.07s);
will-change: opacity, transform;
}
.word-reveal.is-visible .word-reveal__word {
opacity: 1;
transform: translate3d(0, 0, 0);
}
@media (prefers-reduced-motion: reduce) {
html.js .word-reveal[data-word-reveal]:not(.is-ready),
.word-reveal__word {
opacity: 1;
transform: none;
transition: none;
}
}
JavaScript
この分割処理ではスペースを保持し、innerHTML を使用せず、スクリーンリーダーに元の文を公開し、最初の表示後に監視を解除します。
document.documentElement.classList.add("js");
function splitWordReveal(element) {
if (element.dataset.wordRevealReady === "true") return;
const text = element.textContent || "";
const parts = text.split(/(\s+)/);
let wordIndex = 0;
element.textContent = "";
element.setAttribute("aria-label", text.trim());
parts.forEach((part) => {
if (!part.trim()) {
element.appendChild(document.createTextNode(part));
return;
}
const word = document.createElement("span");
word.className = "word-reveal__word";
word.setAttribute("aria-hidden", "true");
word.style.setProperty("--word-index", wordIndex);
word.textContent = part;
element.appendChild(word);
wordIndex += 1;
});
element.dataset.wordRevealReady = "true";
element.classList.add("is-ready");
}
function initWordReveals(selector = "[data-word-reveal]") {
const elements = Array.from(document.querySelectorAll(selector));
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (reduceMotion || !("IntersectionObserver" in window)) {
elements.forEach((element) => {
element.classList.add("is-ready", "is-visible");
});
return;
}
const observer = new IntersectionObserver(
(entries, io) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
entry.target.classList.add("is-visible");
io.unobserve(entry.target);
});
},
{
threshold: 0.2,
rootMargin: "0px 0px -10% 0px",
}
);
elements.forEach((element) => {
splitWordReveal(element);
observer.observe(element);
});
}
document.addEventListener("DOMContentLoaded", () => {
initWordReveals();
});
フレームワークに関する注意
- React/Vue/Svelte:マウント後に分割処理を実行し、ルート変更時にオブザーバーのインスタンスをクリーンアップします。
- Framer Motion:同じトークンを使用します:
y: 20、opacity: 0、duration0.8、ease[0.16, 1, 0.3, 1]、stagger0.06〜0.08、once: true。 - GSAP:
fromTo(words, { y: 20, opacity: 0 }, { y: 0, opacity: 1, duration: 0.8, ease: "expo.out", stagger: 0.07 })を使用します。
デザイン上のルール
- 見出し、小見出し、ラベル、引用文などの短いテキストに使用します。長い段落には使用しないでください。
- 上質で落ち着いた印象にするには、文字ではなく単語ごとに段階的に表示します。
- オフセットは控えめにします。バウンス、回転、大きなぼかしは加えないでください。
- アニメーションは
transformとopacityのみを使用します。 - リンク、ボタン、または意味のあるインラインマークアップを含むテキストは分割しないでください。
- 折り返しが重要な場合は、Web フォントの読み込み後に初期化します。
簡易チェック
- JavaScript が無効でもテキストが表示されます。
- 単語は
translateY(20px)とopacity: 0の状態から始まります。 - 各単語は
0.06s〜0.08sの間隔で一度だけ表示されます。 - スクロールを繰り返してもアニメーションは再生されません。
- 動きを抑える設定を使用するユーザーには、読みやすい静的なテキストが表示されます。
コメント
Markdown 対応