mcpField
S
staggered-word-reveal

staggered-word-reveal

github.com/mengtoデザイン・UI7.1k1.6k 回インストール3 日前に更新
リポジトリ

staggered-word-reveal について

テキストがビューポートに入ると単語ごとに表示する、フロントエンド向けのデザインスキルです。アクセシビリティとモーション軽減に配慮した、一度だけ再生する控えめなアニメーションを案内します。

主な機能

  1. 01単語ごとのテキスト分割と表示
  2. 02ビューポート進入時に一度だけ起動
  3. 03CSSとJavaScriptでの実装
  4. 04スクリーンリーダーとモーション軽減への配慮

活用シーン

  1. 01スクロールに合わせた短い見出しの表示
  2. 02引用文の段階的な表示
  3. 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、duration 0.8、ease [0.16, 1, 0.3, 1]、stagger 0.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の間隔で一度だけ表示されます。
  • スクロールを繰り返してもアニメーションは再生されません。
  • 動きを抑える設定を使用するユーザーには、読みやすい静的なテキストが表示されます。

コメント