等宽文本行

海报风格的锁定,其中每一行都经过缩放,使其跨越容器的整个宽度,可以创建视觉上引人注目的标题。 使用 text-fit 时,浏览器会在布局期间缩放每一行,因此在调整大小、文案更改和字体加载时,文本仍能正确显示。

对于填充其容器的单行(例如标题或引言),请直接对该元素应用 text-fit;本指南涵盖多行组合。

基本实现

对于强制换行:

  • 使用 display: block 将 <span> 中的每一行封装起来。
  • 将 white-space: nowrap 应用于每个强制行 <span>。

实现准则:

  • 在标题本身上使用 text-fit: grow per-line-all <percentage>。
  • 使用 per-line-all,而不是 per-line。per-line 会跳过最后一行以及以强制换行符结尾的任何行,因此,如果每行只有一个文本块,则不会缩放任何内容。
  • 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,使其框紧贴其自身的字形。它会使用缩放后的指标进行修剪。如需了解详情,请参阅精确文本对齐。
  • 使用 Flexbox 或 Grid + gap 在标题上设置行间距,而不是使用 line-height。这样,间距就会保持一致,而不会随着每行字体大小的增加而增加。

如果某些行是自由行,请勿使用 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 时有条件地加载。