等幅のテキスト行

各行がコンテナの幅いっぱいに広がるように拡大縮小されたポスタースタイルのロックアップは、視覚的に印象的な見出しを作成できます。 text-fit を使用すると、ブラウザはレイアウト中に行ごとにスケーリングするため、サイズ変更、コピーの変更、フォントの読み込み時に正しく表示されます。

コンテナ全体を占める 1 行のテキスト(見出しや引用など)の場合は、その要素に直接 text-fit を適用します。このガイドでは、複数行のロックアップについて説明します。

基本的な実装

強制ラインの場合:

  • <span> の各行を display: block でラップします。
  • 各強制ライン <span> に white-space: nowrap を適用します。

実装ガイドライン:

  • 見出し自体に text-fit: grow per-line-all <percentage> を使用します。
  • per-line ではなく per-line-all を使用します。per-line は、最後の行と、強制改行で終わる行をスキップします。そのため、1 行に 1 つのブロックがある場合は、何もスケーリングされません。
  • grow を shrink と組み合わせることはできません。フォールバックとしてより適しているか、計算しやすいかに応じて、ベースの font-size を最大値または最小値に設定し、それに応じて text-fit を shrink または grow に設定します。shrink を使用する理由がない場合は、grow を使用して font-size を最小値に設定することをおすすめします。ほとんどの場合、この方が安全です。
  • 拡大または縮小を制限する場合は、必ずパーセンテージ(例: 300%)をガードレールとして使用します。
  • コンテナには明確なインライン サイズが必要です。text-fit は固有サイズを変更しないため、fit-content / max-content の幅や、コンテンツ サイズの Flex / Grid アイテムには何も行いません。
  • 最初の行の上と最後の行の下の行間をトリミングする。正確なテキスト配置をご覧ください。
  • line-height は 1 以下にしてください。そうしないと、異なるフォントサイズの行間のギャップが大きくなり、扱いにくくなります。
  • 重要: 計算された font-size は変更されないため、em の間隔はスケーリングされません。
  • 孤立行が誤って過度に強調されないようにするには、空き行がある場合に text-wrap: balance または text-wrap: pretty を適用します。

間隔

すべての行を強制的に改行すると、間隔をより細かく制御できます。

  • 各行ブロックに text-box: trim-both cap alphabetic を適用して、ボックスが独自のグリフを囲むようにします。スケーリングされた指標を使用してトリミングします。詳細については、正確なテキスト配置をご覧ください。
  • 行間を調整するには、line-height ではなく、Flexbox または Grid + gap を見出しに使用します。これにより、各行のフォントサイズに応じてギャップが大きくなるのではなく、均一になります。

一部の行が空いている場合は、text-box を使用しないでください。最初と最後の行のみがトリミングされるため、外側の行はグリフに沿って配置されますが、内側のギャップは行間を維持します。line-height ですべての間隔を管理します。

ビジュアル デザイン

  • ロックアップは、行間のフォントサイズに大きな違いがある場合に最も見栄えが良くなります。ほとんど同じ長さの行で構成されるテキストからロックアップを作成することは避けてください。フォントサイズが同じでも、十分に異なっていても、コントラストのデザイン原則に違反することになります。これを実現するには、短い行を強制的に作成します。強調する単語をブロックで折り返し、残りの単語を折り返します。つまり、混合ロックアップです。これは、text-wrap: balance と組み合わせる場合に特に重要です。
  • 欧文文字で使用する場合は、text-transform: uppercase と組み合わせることをおすすめします。アセンダーとディセンダーが、閉じられたスペースを作成したり、隣接する行と重なったりして、見栄えが悪くなる傾向があるためです。
  • 短い行を強制する場合は、強調するとメッセージが引き立つ単語を選びます。
  • 制限事項: テキストを拡大すると、ビジュアル ウェイトも増加し、ストロークがより強く認識されます。現在、これを相殺する方法はありません。たとえば、大きなテキストの font-weight を減らすことはできません。計算に使用する単位がないためです。スケーリングは em などのフォント相対単位に影響しないためです。

代替戦略

対象: text-box

text-box には個別の代替は必要ありません。固定ロックアップでも小さな line-height を維持します。text-box では何も行われず、サポートされていない場合はギャップが狭くなります。ただし、各行のフォントサイズに応じてギャップが拡大します。

対象: text-wrap: balance

対象: text-fit

強制的に改行された行には、短く忠実なフォールバックがあります。各行を基本サイズで測定し、コンテナに対する比率でスケーリングします。上限は同じです。

@supports not (text-fit: grow) {
  .lockup > span {
    /* Shrink-wrap each line so its width is the text width */
    width: fit-content;
    /* --text-fit-scale is measured by the script; the cap mirrors the text-fit percentage */
    font-size: min(var(--text-fit-scale, 1) * 1em, 300%);
  }
}
if (!CSS.supports('text-fit', 'grow per-line-all')) {
  const fit = lockup => {
    const lines = [...lockup.querySelectorAll(':scope > span')];
    // Unscale first: on a refit, offsetWidth would otherwise include the previous scale
    for (const line of lines) line.style.removeProperty('--text-fit-scale');
    // MANDATORY: all reads, then all writes. Interleaving them (e.g. reading
    // lockup.clientWidth inside the write loop) forces one reflow per line.
    const scales = lines.map(line => lockup.clientWidth / line.offsetWidth);
    lines.forEach((line, i) => line.style.setProperty('--text-fit-scale', scales[i]));
  };
  // Refit on resize and font load
  for (const lockup of document.querySelectorAll('.lockup')) {
    new ResizeObserver(() => fit(lockup)).observe(lockup);
  }
}

自由な行には、信頼できるフォールバックがありません(改行はレイアウトまで不明です)。clamp(2rem, 1rem + 5cqi, 5rem) などの流動的な基本サイズを持つバランスの取れた見出しへのプログレッシブ エンハンスメント。混合ロックアップでは、上記のスクリプトは強制された行に適合します。ラッピング ブロックはコンテナと同じ幅で測定されるため、スケール 1 を維持し、流動的なベースサイズのみを取得します。

テキストの調整が重要な場合は、JS ライブラリ(fitty など)の使用を検討してください。text-fit がサポートされていない場合にのみ、条件付きで読み込まれます。