目次
情報確認日:2026年10月16日。GSAP公式資料を参照した、本サイト独自の実装例です。
見出しを一文字ずつ表示するSplitTextの実装を紹介します。日本語や絵文字を自分でsplitしない理由、フォント待機、元のHTMLへ戻す処理、読み上げの注意点を整理します。
スポンサーリンク
文字を動かすには、対象となる要素が必要
通常の見出しは一つのテキストとして表示されています。SplitTextは、その文字や単語、行を個別の要素へ分け、GSAPで扱える対象を用意するプラグインです。箱を順番に動かすstaggerの基本とは違い、文字の分割と元のHTMLへ戻す段階が加わります。
今回作るのは、ボタンを押したときだけ見出しを文字ごとに表示するデモです。CSSの初期状態では普通の見出しとして読めるため、JavaScriptやフォントの取得に失敗しても文章を透明のまま残しません。分割が終わる前から重要な文章を隠す設計は避けます。
日本語を文字列のsplitだけで処理しない
JavaScriptのsplit("")で分けた単位が、人が一文字と認識する単位と常に一致するとは限りません。絵文字や結合文字などを扱う場合は特に注意が必要です。見出しが日本語だから単純な分割で十分と決めず、使う文字と実際の結果を確認してください。
SplitTextのcharsを使えば、プラグインが作った対象をそのままTweenへ渡せます。ただし、禁則処理や字間、ルビ、装飾を含むすべての日本語組版を保証するものではありません。公開する文章を実際に表示し、句読点が行頭へ孤立しないか、見出しの高さが不自然に増えないかを確認します。
スポンサーリンク
最初はcharsだけに対象を絞る
typeへcharsを指定し、使わない単語や行の分割を増やさない構成にしています。文章が長いほど、作られるDOM要素も増えます。ページ全体の本文を文字単位へ分けるより、短い見出しなど、意味のある小さな範囲から始める方が扱いやすくなります。
行ごとに下から現れる表現が必要なら、linesやmaskなど別の設定を検討します。その場合はフォントや幅の変更で行の分かれ方が変わるため、文字だけの例と同じ初期化を流用しないようにします。何を分割したいかを、見た目の完成形から逆に決めます。
フォントを待つ間の連打も処理する
サンプルはdocument.fonts.readyを待ってから分割します。Webフォントの適用前後で幅が変わる場合に、最初の状態だけで処理を始めないためです。ただし、フォントの読み込みに時間がかかっても、通常の見出しは既に読める状態です。
非同期処理にはrequestという番号を付けています。待機中に何度も再生したり、元へ戻すボタンを押したりした場合、古い待機処理が後から分割を実行しないようにします。見た目のTweenを止めるだけでは、まだ開始していない非同期処理は取り消せません。
スポンサーリンク
再生前にTweenと分割の両方を戻す
cleanupはTweenのrevertとSplitTextのrevertを別々に呼びます。前者は動きが加えた状態、後者は分割によって置き換えたHTMLを戻す役割です。文字が二重に入れ子になったり、古い分割結果を新しいTweenへ渡したりしないよう、次の再生前に片付けます。
アニメーションが完了した後も、サンプルでは再生や復元の操作用に参照を保持します。SPAのコンポーネントとして使うなら、破棄時にもrequestを無効化し、cleanupとイベントの解除を呼ぶ関数を用意してください。画面のDOMを削除することと、残っている参照を解放することは別です。
ariaの自動設定が解決する範囲を理解する
aria: "auto"は、元の文章を親のaria-labelへ持たせ、分割した要素を読み上げから隠す方式です。一文字ずつ不自然に読み上げられる問題を減らすための仕組みですが、入れ子になったリンクや強調の意味をすべて保つわけではありません。
今回の見出しには操作できる子要素を入れていません。リンクを含む文章を動かす場合は、分割しない領域を設ける、別の読み上げ用コンテンツを適切に用意するなど、構造自体を見直す必要があります。ariaを指定した事実だけでアクセシビリティ対応済みとは判断しません。
画面幅が変わる場合は、分割する単位で考える
今回の例は行の分割を作らないため、autoSplitを指定していません。行を作る用途でautoSplitを使うなら、作り直された要素に対してTweenを作るonSplitの設計が重要になります。古いlines配列をずっと保持する構成へ、オプション一つを足すだけでは足りません。
フォント待機、分割、再分割、Tween、復元という順序を紙へ書いてみると、残りやすい処理が分かります。行の高さが変わると周囲のスクロール位置にも影響するため、見出しだけでなく直後の本文まで含めて点検してください。
動かさない場合にも見出しを残す
reduced motionでは、分割せず通常の見出しを表示します。durationだけをゼロへ変えて大量の分割要素を作る必要がないため、不要なDOM変更も抑えられます。再生ボタンを押して何も動かないことが分かりにくい場合は、設定に応じた説明文を追加する方法もあります。
確認するのは、短い文章、改行する文章、絵文字を含む文章、フォント取得が遅い状況、待機中の復元、連打です。文字を派手に登場させることより、内容を失わず、必要なときに元のHTMLへ戻せることを基準に実装しましょう。
スポンサーリンク
実装例と配置するファイル
npmパッケージを解決できる既存のViteなどの環境で npm install gsap を実行してください。HTMLはbody内、CSSはスタイルシート、JavaScriptはmain.jsへ置き、body末尾からtype="module"でmain.jsを読み込みます。WordPress本文でコードを実行する例ではなく、開発環境で動かすためのサンプルです。
<section class="text-demo">
<h1 class="headline">伝わる見出しに、少しだけ動きを。</h1>
<p>文章の意味はアニメーションを見なくても理解できます。</p>
<button type="button" id="reveal">文字ごとに表示</button>
<button type="button" id="restore">元のHTMLへ戻す</button>
</section>
.text-demo { max-width:45rem; margin:3rem auto; padding:1rem; }
.headline { font-size:clamp(1.7rem,5vw,3.4rem); line-height:1.6; }
.text-demo button { padding:.7rem 1rem; margin:.3rem; }
.text-demo button:focus-visible { outline:3px solid #5125bb; }
import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText";
gsap.registerPlugin(SplitText);
const headline = document.querySelector(".headline");
let split, tween, request = 0;
function cleanup() {
tween?.revert();
split?.revert();
tween = split = null;
}
async function reveal() {
const token = ++request;
await document.fonts?.ready;
if (token !== request) return;
cleanup();
if (matchMedia("(prefers-reduced-motion: reduce)").matches) return;
split = SplitText.create(headline, { type: "chars", aria: "auto" });
tween = gsap.from(split.chars, {
y: 18, opacity: 0, duration: 0.45, ease: "power2.out",
stagger: { amount: 0.55 }
});
}
document.querySelector("#reveal").addEventListener("click", reveal);
document.querySelector("#restore").addEventListener("click", () => {
request++;
cleanup();
});
公開前の確認
掲載コードはGSAP 3.15.0を用いたDOM模擬環境で初期化と主要操作を確認しています。実ブラウザーの描画、フォントの改行、タッチの挙動、支援技術による読み上げは別途確認が必要です。開発用の環境だけでなく、対象端末と本番用ビルドで、初期表示・連打・停止・画面の再表示を確認してください。
スポンサーリンク



![JavaScriptモダンプログラミング完全ガイド [堅牢なコードを効率的に開発できる! ]](https://m.media-amazon.com/images/I/91NvEBSfCkL._SL1500_.jpg)