テキストのレイアウトが変わったときに再構築して、SplitTextの行グループを正確に保ちます。対応するトリガーには自動分割を使い、タイポグラフィの変更には明示的な再構築を追加して、Macのデスクトッププレビューで分割前のブラウザテキストと結果を比較します。

このガイドは、GSAP SplitTextと流動的な見出しを使ったブラウザページを編集する人向けです。見出しの幅を固定したままフォントサイズが変わる場合も含め、生成された各行のラッパーを、その視覚的な行に現在含まれる単語と一致させることが目的です。

流動的なタイポグラフィで行グループがずれるのはなぜ?

生成された行ラッパーは、特定の単語グループを囲みます。利用可能なスペースやタイポグラフィが変わった後は、そのグループを再構築する必要がある場合があります。GSAPのドキュメントによると、行が分割対象に含まれている場合、フォントの読み込み完了時と幅の変更時にはautoSplit: trueが使えます。これらのトリガーが、あらゆるタイポグラフィの変更を監視する汎用ウォッチャーになるわけではありません。SplitTextのドキュメント

font-size: clamp(2rem, 4vw, 4rem)を使い、幅が42remを上限とするコンテナ内に見出しを置く場合を考えてみましょう。MDNによると、clamp()は最小値と最大値の範囲内に収まる推奨値を定義します。この場合、コンテナが上限に達した後も、推奨フォントサイズはビューポート幅に応じて変化します。clamp() CSS関数

次の計算では、ルートフォントサイズを説明用に16 CSSピクセルとし、コンテナが上限に達するのに十分なスペースがあることを前提としています。これらは計算上の寸法であり、実測したブラウザの結果や予測される行数ではありません。

ビューポート幅 上限に達したコンテナ幅 計算上のフォントサイズ
900 CSS px 672 CSS px 36 CSS px
1200 CSS px 672 CSS px 48 CSS px
1600 CSS px 672 CSS px 64 CSS px

コンテナの幅は変わらない一方で、文字は大きくなります。実務上は、幅の監視だけではこのケースをカバーできないと考えられます。算出フォントサイズの変化も再構築の理由として扱い、その後に単語の配置を確認してください。

同じ寸法とスタイルにおける、ブラウザの分割前の描画を基準に正確さを判断します。見出しの行数が当初のままになることを条件にしないでください。流動的なレイアウトでは行数が変わることがあります。

アニメーションを変更する前に分割前の基準を用意する

ローカルプレビューに、短いテスト用見出しを2つ用意します。幅、フォント、サイズ、太さ、字間、折り返しルールを同じにします。1つ目には手を加えず、2つ目にSplitTextを適用します。この一時的な比較により、特定の単語がどこで折り返されるべきかを決めつけずに、古いグループ分けを見つけられます。

まずは通常の空白と折り返しを使います。W3C CSS Text仕様は、空白の折りたたみとソフトラップをwhite-spaceがどのように制御するかを定義し、改行と字間の個別ルールも定めています。再構築では、手動で改行を挿入するのではなく、意図したルールを保つようにします。CSS Text Module Level 3

ウェブフォントを使うページでは、最初に分割する前にdocument.fonts.readyを待ちます。MDNによると、このPromiseは関連するフォント読み込みとレイアウト処理が完了した後に解決されます。これにより初回の計測時点を定められますが、その後のレイアウト変更を固定できるわけではありません。FontFaceSet: readyプロパティ

ブラウザで実際に使われているフォントも確認してください。MDNは、使用中のフォントと宣言済みのすべてのフォントを区別しています。たとえば、任意指定のフォントは読み込み可能な時間内に読み込めなければ使われないことがあります。読み込み完了を待っても、宣言したすべてのフォントが描画に使われた証明にはなりません。Document: fontsプロパティ

自動分割と流動的なサイズの明示的なチェックを実装する

次の完全なブラウザモジュールは、分割前の基準、アニメーションするコピー、Toggle spacingボタンを作成します。npmインポートを解決でき、トップレベルのawaitをサポートし、ドキュメントのbodyができてからブラウザ用エントリーポイントを実行する、既存のJavaScriptプロジェクトで使い捨てのプレビュー用ページを使ってください。MacにNode.jsとnpmをインストールしておきます。

プロジェクトのディレクトリで次のコマンドを実行し、GSAPをインストールします。このコールバック方式を使うにはGSAP 3.13以降が必要です。GSAPのインストール手順では、npmによるインストールとプラグインの明示的な登録が説明されています。インストール

npm install gsap

使い捨てのプレビュー用ページのブラウザエントリーモジュールを、次のコードに置き換えます。初期化には、ドキュメントにあるSplitText.create()パターンを使っています。フェードではgsap.from()を使い、透明度0から既存の見た目へアニメーションします。SplitText.create()、gsap.from()

import { gsap } from 'gsap';
import { SplitText } from 'gsap/SplitText';

gsap.registerPlugin(SplitText);

const style = document.createElement('style');
style.textContent = `
.flow-demo {
  width: min(100%, 42rem);
  font-family: system-ui, sans-serif;
}
.flow-demo h2 {
  font-size: clamp(2rem, 4vw, 4rem);
  line-height: 1.15;
  font-weight: 600;
  margin: 1rem 0;
  white-space: normal;
  text-wrap: wrap;
}
.flow-line { outline: 1px solid #aaa; }
`;
document.head.append(style);

const host = document.createElement('section');
host.className = 'flow-demo';
const reference = document.createElement('h2');
reference.textContent =
  'Panel Alpha holds a series of soft shapes beside Panel Beta.';
const target = reference.cloneNode(true);
const button = document.createElement('button');
button.type = 'button';
button.textContent = 'Toggle spacing';
host.append(reference, target, button);
document.body.append(host);

await document.fonts.ready;

const split = SplitText.create(target, {
  type: 'lines',
  linesClass: 'flow-line',
  autoSplit: true,
  onSplit(self) {
    return gsap.from(self.lines, {
      opacity: 0,
      duration: 0.3,
      stagger: 0.04
    });
  }
});

let timer;
let fontSize = getComputedStyle(target).fontSize;

function checkFluidSize() {
  clearTimeout(timer);
  timer = setTimeout(() => {
    const next = getComputedStyle(target).fontSize;
    if (next !== fontSize) {
      fontSize = next;
      split.split();
    }
  }, 150);
}
window.addEventListener('resize', checkFluidSize);

let spaced = false;
button.addEventListener('click', () => {
  spaced = !spaced;
  reference.style.letterSpacing = target.style.letterSpacing =
    spaced ? '0.06em' : 'normal';
  split.split();
});

アニメーションはonSplit()内で作成し、SplitTextがクリーンアップできるように、また再構築時に同期できるように返します。GSAPのドキュメントでは、このコールバック方式はバージョン3.13以降で使えます。SplitTextのドキュメント

追加したリサイズハンドラーは算出フォントサイズだけを確認し、リサイズが一段落してから再構築を要求します。150ミリ秒の遅延は、説明用に選んだ実装上の値です。この例が目指すのはリサイズ後に正確なグループを作ることであり、リサイズ中のあらゆるフレームで正しいグループ分けを保証することではありません。

間隔ボタンは、2つ目の明示的なトリガーを示します。両方のコピーを変更してから、split.split()を呼び出します。GSAPのドキュメントによると、このメソッドは必要に応じて既存の分割を先に元に戻します。この小さな例では、幅の変更とフォントサイズの変更による再構築要求が重複することもあります。大きなページでプロファイリングにより不要な処理が見つかった場合は、まとめてください。split()

このモジュールはJavaScriptの構文チェックに合格し、説明用の寸法計算も実行しました。ここではブラウザのレイアウトとアニメーションを実行していません。ローカルプレビューで以下の目視チェックを完了してください。

レイアウトの変更に合わせて再構築のトリガーを選ぶ

このサンプルは、流動的なフォントサイズ式を1つ監視します。これを応用する場合は、テキストを変更するコードやレイアウトイベントを特定し、その変更が起きる箇所に再構築を入れてください。このサンプルが、無関係なクラス変更、コンテナに応じたタイポグラフィ、連続してアニメーションするフォントプロパティまで検出すると想定しないでください。

ページ内での変更 推奨される対応
テキスト要素の幅 ドキュメントに記載された自動分割動作を有効にして、結果を確認します。
テキスト幅は固定で、ビューポートに応じてフォントサイズが変わる リサイズ後に算出サイズを比較し、変化した場合に再構築します。
コントロールが字間、太さ、フォント設定を変える コントロールが新しいスタイルを適用した後に再構築を要求します。
タイポグラフィが周囲のコンテナに依存する サイズを決めるコンテナを監視し、関連する算出タイポグラフィを確認します。
非表示だったコンポーネントが表示される 意図した描画寸法になってから初期化または再構築します。

コンテナに応じたレイアウトでは、ResizeObserverを使うとビューポートのリサイズとは独立して要素サイズの変化を検出できます。サイズ決定の入力元となるコンテナを監視してください。テキストボックスだけを監視しても、算出スタイルのあらゆる変更を通知するものとは見なせません。ResizeObserver

監視対象の高さが変わるたびに、無条件で再構築するのは避けてください。再構築自体がレイアウトに影響することがあります。MDNはResizeObserverの循環を説明しており、フィードバックループを防ぐには依存関係を制御する必要があるとしています。処理を別フレームに遅らせても、根本にある無限ループは解消されません。関連する入力を比較してから、次の再構築を要求してください。ResizeObserverの監視エラー

見出しのテキストを変更する場合は、内容を置き換える前に古い分割を元に戻し、新しい元テキストから新しいインスタンスを初期化します。GSAPによると、revert()は分割前の元のコンテンツを復元します。生成されたラッパーを編集してから元に戻すと、その編集が破棄されるおそれがあります。revert()

ブラウザの文字整形を変えずに折り返しを確認する

Macのブラウザでローカルプレビューを開き、ウィンドウをゆっくりリサイズしてから操作を止めます。再構築とフェードが落ち着いた後、両方のコピーで各行の最後の単語を比較します。コンテナに上限が適用されたときと、幅が狭まるときの両方で繰り返してください。それぞれのサイズでToggle spacingをクリックします。

コピーが一致しない場合は、次の手順で診断します。

  1. 両方のコピーで、利用可能な幅と算出タイポグラフィが同じであることを確認します。
  2. 現在のサイズで再構築します。それで不一致が解消した場合は、欠けているトリガーを調べます。
  3. 不一致が残る場合は、生成された行ラッパーのスタイルを取り除いて再度テストします。
  4. 分割前のテキストに戻し、元のCSSが意図どおりに折り返すことを確認します。
  5. 一度に1つのレイアウト入力だけを変更して、差異を再現できるようにします。

文字の整形はブラウザのテキストエンジンに任せてください。W3Cは、インライン要素の境界をまたいで整形を続ける場合と、区切る必要がある場合を定めており、その中にはインラインのパディング、ボーダー、マージンで区切る場合も含まれます。つまり、ラッパーのスタイルが計測対象に影響することがあります。以前のスクリーンショットを再現するために、文字ごとのボックス間に間隔を加えるのは避けてください。要素境界をまたぐ文字整形

innerTextを改行文字で分割して、視覚的な行数を数えないでください。WHATWGはinnerTextを、テキスト、明示的な改行、構造上の境界に関する収集ルールを伴う描画テキストとして定義しています。ソフトラップされた視覚的な行の配列を提供するものではありません。分割前のブラウザ描画を比較基準として使ってください。innerTextおよびouterTextプロパティ

ライブラリのドキュメントにある制限を守ってください。GSAPは、分割時にtext-wrap: balanceを使わないよう推奨しており、SplitTextはSVGテキストノード向けに設計されていないと説明しています。均等割り付けの見出しでは、テキストを分割せず要素全体をアニメーションし、行ラッパーを使う場合はHTMLテキスト要素に通常の折り返しを使ってください。SplitTextのヒントと制限

実際の幅やタイポグラフィの変更を通して、分割したコピーが分割前のコピーに沿うことを確認したら完了です。再構築が必要な変更の近くに明示的な処理を置き、診断中は基準用のコピーを残し、見出しを完成ページに組み込む前に一時的な複製と行のアウトラインを削除してください。

出典