Строка текста с фрагментами разного размера может превышать значения line-height обоих фрагментов, поскольку выравнивание по базовой линии объединяет их верхние и нижние границы. В этом руководстве показано, как диагностировать дополнительную высоту и настроить строку из двух подписей в настольном браузере на Mac.
Почему высоту строки не определяет самый высокий фрагмент?
Представьте заголовок страницы с крупной подписью Label A рядом с мелкой Label B. У обоих фрагментов задано line-height: 40px, однако высота содержащей их строки превышает 40 CSS-пикселей. Это объясняется тем, как при встроенной раскладке выравниваются блоки перед вычислением охватывающей их строки.
Для обычного встроенного текста браузер вычисляет вклад каждого блока в высоту, вертикально выравнивает блоки, а затем охватывает их совокупные границы. При выравнивании по базовой линии самая верхняя граница может принадлежать одному фрагменту, а самая нижняя — другому. Поэтому итоговая высота может превышать вклад любого отдельного фрагмента. Родительский элемент также вносит невидимый блок минимального размера, традиционно называемый распоркой. W3C: вычисление высоты строки
Базовая линия служит ориентиром для выравнивания текста. Она не проходит посередине вклада его line-height. Одинаковая высота строки для текста разного размера не означает, что у обоих фрагментов одинаковое расстояние над базовой линией и под ней. Начальное значение vertical-align — baseline. MDN: vertical-align
Различайте три величины: метрики восходящих и нисходящих элементов шрифта, видимые границы конкретных букв и пространство, занимаемое при раскладке строки. Цветной фон или прямоугольник выделения фрагмента нельзя считать прямым измерением охватывающего его блока строки: CSS различает геометрию отрисовки встроенных элементов и расчёт высоты строки. W3C: интерлиньяж и половинный интерлиньяж
Как из двух вкладов по 40px получается строка высотой 48,4px?
Для обычного текста, выровненного по базовой линии и имеющего явную высоту строки, полезно выполнить такой расчёт: вычесть сумму восходящей и нисходящей метрик шрифта из высоты строки, а затем поровну распределить остаток над и под ними. Это дополнительное пространство называется интерлиньяжем, а каждая его половина — половинным интерлиньяжем. В рабочем черновике CSS Inline Layout Level 3 описан этот расчёт и допускается отрицательный интерлиньяж. Статус черновика не гарантирует поддержку браузерами всех новых свойств, которые он содержит. W3C: расчёт границ раскладки
Следующие значения иллюстративны и не являются измерениями Arial или какого-либо шрифта для Mac. Предположим, что вклад родительской распорки совпадает с вкладом малого фрагмента и что другого встроенного содержимого или вертикальных смещений нет.
| Вклад | Восходящая метрика шрифта | Нисходящая метрика шрифта | Высота строки | Половинный интерлиньяж | Над базовой линией | Под базовой линией |
|---|---|---|---|---|---|---|
| Крупный фрагмент | 32px | 8px | 40px | 0px | 32px | 8px |
| Малый фрагмент | 9.6px | 2.4px | 40px | 14px | 23.6px | 16.4px |
| Родительская распорка | 9.6px | 2.4px | 40px | 14px | 23.6px | 16.4px |
В этом примере с выравниванием по базовой линии независимо возьмите наибольшую величину с каждой стороны: max(32, 23.6, 23.6) + max(8, 16.4, 16.4) = 48.4px. Вклад каждого элемента равен 40px, но верхнюю границу задаёт крупный фрагмент, а нижнюю — малый фрагмент и родитель. Этот пример расчёта основан на описанном алгоритме раскладки и не является прогнозом измерения в браузере. W3C: модель встроенной раскладки
Поэтому при диагностике нужно спросить: какой участник задаёт каждую границу? Уменьшение высоты строки малого фрагмента может убрать его вклад в нижнюю границу, но такой же вклад от родительской распорки останется.
Как воспроизвести и проверить это на Mac?
В этой инструкции используйте Chrome на Mac. Откройте новую вкладку, перейдите на about:blank, затем нажмите Command + Option + J, чтобы открыть Console. Chrome описывает это сочетание клавиш как способ запускать JavaScript. Chrome DevTools: обзор
Выполните в этой пустой вкладке всю приведённую ниже программу. Она создаёт три искусственные строки с одинаковыми подписями, выводит результаты измерений и использует теневой корень, чтобы объединить стили демонстрации. Установка пакетов и внешние библиотеки не требуются. Повторный запуск заменит предыдущую демонстрацию.
(async () => {
document.getElementById('line-box-demo')?.remove();
const host = document.createElement('div');
host.id = 'line-box-demo';
document.body.append(host);
const root = host.attachShadow({ mode: 'open' });
const style = document.createElement('style');
style.textContent = `
:host { display: block; padding: 24px; color: #222; background: #fff; }
.label { margin: 20px 0 6px; font: 14px Arial, sans-serif; }
.row { font: 12px/40px Arial, sans-serif; white-space: nowrap;
margin: 0; padding: 0; border: 0; background: #e8eef4; }
.large { font-size: 40px; line-height: 40px; }
.small { font-size: 12px; line-height: 40px; }
.trim-span .small, .trim-both .small { line-height: 12px; }
.trim-both { font-size: 40px; }
`;
root.append(style);
const cases = [
['', 'Shared 40px line-height'],
['trim-span', 'Smaller span only'],
['trim-both', 'Parent and smaller span']
];
const rows = cases.map(([className, label]) => {
const caption = document.createElement('p');
caption.className = 'label';
caption.textContent = label;
const row = document.createElement('div');
row.className = `row ${className}`;
for (const [size, text] of [['large', 'Label A'], ['small', ' Label B']]) {
const span = document.createElement('span');
span.className = size;
span.textContent = text;
row.append(span);
}
root.append(caption, row);
return { label, row };
});
await document.fonts.ready;
console.table(rows.map(({ label, row }) => ({
case: label,
parentLineHeight: getComputedStyle(row).lineHeight,
largeLineHeight: getComputedStyle(row.children[0]).lineHeight,
smallLineHeight: getComputedStyle(row.children[1]).lineHeight,
rowHeight: row.getBoundingClientRect().height
})));
})();
Программа ждёт document.fonts.ready: этот объект разрешается после загрузки шрифтов документа и связанной с ней работы раскладки. Если вы позже добавите другой шрифт или измените содержимое, выполните измерение повторно после завершения этих изменений. MDN: FontFaceSet.ready
Сравните три строки: условия в них меняются последовательно. В первой высота строки у обоих фрагментов и родительского элемента равна 40px. Во второй высота строки уменьшена до 12px только у малого фрагмента. В третьей размер шрифта родительского элемента также изменён на 40px, чтобы его распорка соответствовала размеру шрифта и высоте строки крупного текста. Если вторая строка остаётся высокой, а третья становится ниже, это сравнение указывает на вклад родительского элемента.
Ориентируйтесь на результаты измерений в браузере, а не на иллюстративное значение 48,4px. Программа измеряет каждую строку с помощью getBoundingClientRect(), который возвращает ограничивающий прямоугольник элемента, включая внутренние поля и границы. В данном случае у строк их нет, каждая содержит одну строку текста без переноса и имеет автоматическую высоту, поэтому измеренная высота служит полезным приближением к высоте блока строки. Измерение фрагмента с помощью того же API вернёт прямоугольник этого фрагмента, но напрямую не покажет ограничивающий блок строки. MDN: getBoundingClientRect()
Что проверить в существующей раскладке?
Щёлкните нужный текст, удерживая Control, и выберите Inspect. Во вкладке Elements проверьте оба фрагмента и содержащий их блок. Во вкладке Styles проследите, откуда берутся объявления, а во вкладке Computed посмотрите применённые значения; при необходимости включите Show All, чтобы проверить унаследованные свойства. Chrome описывает эти представления и фильтры свойств. Chrome DevTools: справочник по функциям CSS
Запишите значения font-family, font-size, line-height, display и vertical-align для каждого участника. Начните с родительского элемента, а не только с двух фрагментов. Затем выполните следующие действия, чтобы определить причину:
- Уточните модель раскладки. Определите, участвуют ли дочерние элементы в обычном потоке встроенных элементов или являются элементами flex- или grid-контейнера.
- Убедитесь, что строка одна. Используйте широкую тестовую область и короткие искусственные подписи, чтобы перенос текста не влиял на измерения.
- Проверьте выравнивание. Прежде чем применять упрощённый расчёт выше, временно уберите смещения от базовой линии и нестандартное вертикальное выравнивание.
- Изменяйте по одному вкладу. Уменьшите высоту строки малого фрагмента и измерьте результат, затем отдельно проверьте вклад шрифта и высоты строки родительского элемента.
- Верните реалистичные условия. После определения причины повторно проверьте результат с нужным шрифтом, доступной шириной и текстом.
Также проверьте наследование. Единичное значение line-height: 1.2 передаёт множитель, который каждый потомок применяет к собственному размеру шрифта. Длина вроде 40px передаёт фиксированную величину, если её не переопределить; проценты вычисляются относительно размера шрифта элемента, в котором они объявлены. Из-за этих вариантов у мелкого текста может быть неожиданно просторный интерлиньяж. Значение normal зависит от шрифта и браузера, поэтому на него нельзя полагаться как на фиксированную числовую величину. MDN: значения line-height и наследование
Какую корректировку выбрать?
Выбирайте корректировку с учётом назначения двух подписей. К компактному заголовку из отдельных подписей предъявляются иные требования, чем к словам внутри абзаца с переносом строк.
| Желаемый результат | Что попробовать | Что проверить |
|---|---|---|
| Сохранить общую базовую линию текста | Задать малому фрагменту подходящую высоту строки и согласовать метрики шрифта родительского элемента с основным текстом | Родительская распорка больше не задаёт лишнюю нижнюю границу |
| Выровнять верхние границы отдельных подписей | Проверить vertical-align: top для обоих встроенных фрагментов |
Изменённое положение базовых линий соответствует дизайну |
| Расположить отдельные подписи по центру строки | Использовать flex-контейнер со свойством align-items: center |
Поведение на узком экране и перенос элементов подходят раскладке |
| Сохранить непрерывный поток абзаца | Оставить встроенный текст и задать продуманные наследуемые значения высоты строки | Все получившиеся строки вмещают текст разного размера |
vertical-align: top выравнивает элемент и его потомков по верхнему краю строки. У middle другое определение: свойство выравнивает середину элемента относительно базовой линии родителя плюс половина высоты его строчной буквы x. Поэтому оно не означает геометрическое центрирование по всей строке, а изменение выравнивания не гарантирует определённую итоговую высоту. MDN: значения vertical-align
Для двух независимых подписей flexbox позволяет явно задать выравнивание по поперечной оси с помощью align-items. Центрирование и выравнивание по базовой линии отражают разные дизайнерские решения. Переход на flexbox меняет контекст раскладки, поэтому используйте его, когда подписи являются отдельными элементами, и проверьте результат на узких экранах. MDN: align-items
Меньшая высота строки также может привести к тому, что текст будет отрисовываться за пределами своего вклада в раскладку. После уменьшения интервалов проверьте соседние строки: компактный измеренный блок сам по себе не гарантирует, что текст помещается без наложения. W3C: интерлиньяж и половинный интерлиньяж
Могут ли измерения canvas заменить раскладку текста в браузере?
Измерения canvas отвечают на смежный вопрос. WHATWG определяет measureText() и различает метрики восходящих и нисходящих элементов шрифта и фактические границы конкретного текста. Эти измерения полезны при рисовании на canvas, но измерение границ видимых символов не учитывает родительскую распорку в DOM, выравнивание встроенных элементов и их вклад в высоту строки. Не используйте его отдельно для предсказания высоты этой строки. WHATWG: TextMetrics
При экспериментах сохраняйте границы формирования текста. CSS требует продолжать формирование текста между границами встроенных элементов, если форматирование фактически не меняется; при некоторых границах, включая нестандартное вертикальное выравнивание, формирование должно прерываться. Не оборачивайте каждый символ отдельно ради измерения: разделение графемных кластеров или лигатур может привести к непоследовательным результатам. Сохраняйте подписи целиком или в виде осмысленных фрагментов текста. W3C: обработка текста и формирование на границах элементов
Чтобы устранить лишнюю высоту, определите, какой участник задаёт верхнюю границу и какой — нижнюю, включая родительский элемент. Измените их вклад в высоту строки или выберите модель выравнивания, соответствующую назначению подписей, а затем повторно измерьте всю строку с нужным текстом и шрифтом.
Источники
- Подробности модели визуального форматирования — W3C.
- Модуль CSS Inline Layout Level 3 — W3C.
- Свойство CSS line-height — MDN Web Docs.
- Свойство CSS vertical-align — MDN Web Docs.
- Обзор Chrome DevTools — Google for Developers.
- Справочник по функциям CSS — Google for Developers.
- FontFaceSet: свойство ready — MDN Web Docs.
- Element: метод getBoundingClientRect() — MDN Web Docs.
- Свойство CSS align-items — MDN Web Docs.
- Стандарт HTML: Canvas — WHATWG.
- Модуль CSS Text Level 3 — W3C.