HTML и CSS Переводы

Глубокое погружение в CSS: показатели шрифтов, line-height и vertical-align

Line-height и vertical-align — это простые CSS свойства. Они настолько просты, что большинство из нас убеждено в том, что в полной мере понимают, как они работают и как их использовать. Но это не так. В действительности же они сложны. Можно даже считать, что они являются самыми сложными свойствами, т.к. они играют важную роль в создании мало известной особенности CSS: в строчном форматировании текста.

Например, line-height может быть установлен в качестве длины или безразмерного значения, но по умолчанию у него задано значение normal. Окей, но что такое normal? Пишут, что чаще всего это 1 или может даже 1.2. Но даже в спецификации по CSS нет ясного ответа на этот вопрос. Мы знаем, что line-height — безразмерное свойство относительно родительского значения font-size, но проблема заключается в том, что font-size: 100px выглядит по разному, в зависимости от семейства шрифта. Вопрос: значение line-height всегда одинаковое или разное? Действительно ли значение варьируется между 1 и 1.2? А vertical-align? Каково его влияние в отношении line-height?

Глубокое погружение в CSS не такой простой механизм…

Давайте сперва поговорим о font-size

Посмотрите на простой HTML код. Тег p содержит три span, каждый из которых имеет свой собственный font-family:

<p>
    <span class="a">Ba</span>
    <span class="b">Ba</span>
    <span class="c">Ba</span>
</p>
p  { font-size: 100px }
.a { font-family: Helvetica }
.b { font-family: Gruppo    }
.c { font-family: Catamaran }

Используя некоторое значение font-size с различными font-family, мы получаем элементы разной высоты:

Разные font-family, одинаковое значение font-size, получаем разную высоту элементов

Разные font-family, одинаковое значение font-size, получаем разную высоту элементов

Почему font-size: 100px не создает элементы с высотой в 100px? Я измерил и получилось:

  • Helvetica — 115px;
  • Gruppo — 97px;
  • Catamaran — 164px.

 

Элементы со значением font-size:100px имеют разную высоту

Элементы со значением font-size:100px имеют разную высоту

Все это кажется немного странным, на первый взгляд, но это вполне объяснимо. Причина находится внутри самого шрифта. Вот как это работает:

  • шрифт определяется его em-квадратом (или единицей em) своего контейнера, в котором будет нарисован каждый символ. Этот квадрат использует относительные единицы и обычно устанавливает значение на уровне 1000 единиц. Но здесь также может применяться значение 1024, 2048 и т.д.
  • устанавливается на основе своих относительных единиц, показателей шрифтов (ascender, descender, capital height, x-height и т.д.). Обратите внимание, что некоторые значения могут выходить за пределы em-квадрата.
  • в браузере относительные единицы масштабируются, чтобы нужный размер соответствовал шрифту.

 

Давайте возьмем шрифт Catamaran и откроем его в FontForge, чтобы получить его показатели:

  • em-квадрат 1000;
  • Ascender — 1100 и Descender — 54. После выполнения некоторых тестов, окажется, что браузеры используют значения HHead Ascent/Descent  на Mac OS, а также значения Ascent/Descent на Windows (эти значения могут отличаться!). Отметим также, что прописная буква имеет высоту 640px и высота обычной буква составляет 485px.

 

Значения шрифта в FontForge

Значения шрифта в FontForge

Это значит, что шрифт Catamaran использует 1100 + 540 единиц при 1000 единиц в em-квадрате, который дает высоту 164px, в то время, как в настройках указано font-size: 100px. Эта вычисленная высота определяется как контент-область (content-area) элемента и я буду использовать этот термин далее в этой статье. Вы можете думать о контент-области, как об области, для которой применяется свойство background.

Мы также можем увидеть, что заглавные буквы имеют высоту 68px (680 единиц) и строчные буквы (x-height) — 49px (485 единиц).  В результате, 1ex = 49px и 1em = 100px, а не 164px (к счастью, em основан на размере шрифта, а не на вычисленной высоте).

Шрифт Catamaran: эквивалент UPM и пикселей при использовании font-size: 100px

Шрифт Catamaran: эквивалент UPM и пикселей при использовании font-size: 100px

Прежде чем идти дальше, оставлю некоторое пояснение. Когда элемент <p> отображается на экране, то он может состять из нескольких линий, в соответствии с его шириной. Каждая строка состоит из одного или нескольких inline-элементов (HTML-теги или встроенные imline-элементы для текстового содержимого) и имеет название line-box. Высота line-box основана на высоте его дочерних элементов. Таким образом, браузер вычисляет высоту для каждого inline-элемента, и, следовательно, высоту line-box (от высшей точки своего дочернего элемента к самой нижней точке своего дочернего элемента). В результате line-box — достаточно высок, чтобы содержать все свои дочерние элементы (по умолчанию).

Каждый HTML-элемент на самом деле является неким множеством, состящим из line-boxes. Если вы знаете высоту каждого line-box, Вы знаете высоту элемента.

Если обновить предыдущую страницу HTML код, то получим:

<p>
    Good design will be better.
    <span class="a">Ba</span>
    <span class="b">Ba</span>
    <span class="c">Ba</span>
    We get to make a consequence.
</p>

 

Он будет генерировать три line-boxes:

  • первый и последний, каждый из которых содержит единственный встроенный элемент (содержание текста)
  • второй, содержащий два встроенных элемента и три<span>
Тег <р> (черная рамка) сделана из line-boxes (белые границы), которые содержат inline-элементы (сплошные границы) и анонимные inline-элементы (пунктирные границы)

Тег <р> (черная рамка) сделана из line-boxes (белые границы), которые содержат inline-элементы (сплошные границы) и анонимные inline-элементы (пунктирные границы)

Мы ясно видим, что второй line-box выше остальных, в связи с вычисленной контент-областью его дочерних элементов, один из которых использует шрифт Catamaran.

Трудным в создании line-box является то, что мы не можем видеть и управлять им с помощью CSS. Даже применяя background с псевдоклассом ::first-line не дает нам ни какой визуальной подсказки о высоте line-box.

line-height: к проблемам и за ее пределами

Ранее я ввел два понятия: контент-область (content-area) и line-box. Если вы читали внимательно, то я сказал, что высота line-box вычисляется в соответствии с его дочерней высотой, я не сообщал дочерним элементам высоту их контент-области. В этом и заключается больше различие.

Хотя это может показаться странным, inline-элемент имеет две разные высоты: высоту content-area и высоту virtual-area (Я изобрел термин virtual-area , обозначая таким образом высоту, невидимую для нас. Этого термина Вы не найдете в спецификации).

  • высота content-area определяется показателями шрифта (как показано выше)
  • высота virtual-area — этоline-height , а также это высота, которая используется для вычисления высоты line-box
Inline-элементы, имеющие две разные высоты

Inline-элементы, имеющие две разные высоты

Это ломает распространенное мнение о том, что line-height это расстояние между базовыми линиями (baselane). В CSS это не так.

В CSS, the line-height - это не расстояние между двумя базовыми линиями

В CSS, the line-height — это не расстояние между двумя базовыми линиями

Рассчитанная разность высот между virtual-area и content-area называется ведущей. Половина этой ведущей добавляется к верхней части content-area, другая часть — к нижней. Content-area располагается всегда в центре virtual-area.

На основе его значений, line-height (virtual-area) может быть больше, меньше или равной content-area. В случае меньшего размера virtual-area, ведущее значение станет отрицательным и line-box стане визуально меньше его дочернего элемента.

Существуют также другие виды inline-элементов:

  • inline-элементы (<img>, <input>, <svg>, etc.)
  • inline-block и все inline-* элементы
  • inline-элементы, которые используются в качестве форматирования текста (например, во flexbox-элементах)

Для этих inline-элементов, высота вычисляется на основе height, margin и border. Если height имеет значение auto, то line-height используется в content-area и строго равна line-height.

Inline замещаемые элементы, inline-block/inline-* и blocksified inline элементы имеют контент-область, равную их высоте или высоте линии

Inline замещаемые элементы, inline-block/inline-* и blocksified inline элементы имеют контент-область, равную их высоте или высоте линии

Итак, вернемся к FontForge. Em-квадрат шрифта Catamaran равен 1000, но мы видим значения ascender/descender:

  • Ascent/Descent: ascender — 770 и descender — 230. Используется для символьных рисунков. (табл. “OS/2”)
  • показатели Ascent/Descent: ascender — 1100 и descender — 540. Используется для высоты content-area. (табл. “hhea” и табл. “OS/2”)
  • показатель Line Gap. Используется дл line-height: normal, путем добавления этого значения показателей Ascent/Descent. (табл. “hhea”)

В нашем случае, шрифт Catamaran определяет разрыв в 0 единиц для line gap, поэтому line-height: normal будет равен content-area, который составляет 1640 единиц, иначе 1.64.

В качестве сравнения, шрифт Arial описывает em-квадрат в 2048 единиц, ascender — 1854, descender — 434 и line gap составляет 67. Это означает, что font-size: 100px дает content-area — 112px (1117 единиц) и line-height: normal — 115px (1150 единиц, иначе 1.15). Все эти показатели шрифта устанавливаются разработчиком шрифта.

Становится очевидным, что установка line-height: 1 плохая практика.Я хотел бы напомнить вам, что безразмерное значение относительно для font-size и virtual-area меньше, чем content-area. Именно это и является источником многих наших проблем.

Используя line-height: можно создать line-box меньше, чем контент-область

Используя line-height: можно создать line-box меньше, чем контент-область

Уточнения о вычислении line-box:

  • для inline-элементов, padding и border увеличивают область фона, но не высоту content-area (или высоту line-box).Сontent-area — это не всегда то, что ты видишь на экране. margin-top и margin-bottom не имеют ни какого эффекта.
  • для inline-элементов, inline-block и blocksified inline-элементы: padding, margin и borderувеличивают height, а значит увеличивают content-area и высоту line-box

vertical-align: свойство, которое управляет всеми

Я еще не упомянул о свойстве vertical-align, несмотря на то, что оно важно для вычисления высоты line-box. Можно даже сказать, что vertical-align играет ведущую роль в форматировании текста.

Значение по умолчанию — baseline. Помните о показателях шрифта ascender и descender? Эти значения определяют, где базовая линия стоит, а значит определяет и ее соотношение. Т.к. соотношение между ascenders и descenders редко бывает 50:50.

Начнём с кода:

<p>
    <span>Ba</span>
    <span>Ba</span>
</p>
p {
    font-family: Catamaran;
    font-size: 100px;
    line-height: 200px;
}

Тег <p> с двумя <span> унаследовал значения font-family, font-size и имеют постоянный line-height. Базовые линии (baselines) будут соответствовать и высота line-box  будет равна их line-height.

Те же значения шрифта, одинаковые исходные условия, кажется, что все в порядке

Те же значения шрифта, одинаковые исходные условия, кажется, что все в порядке

Что делать, если второй элемент имеет меньший font-size?

span:last-child {
    font-size: 50px;
}

Как ни странно, но по умолчанию базовая линия может выровнять выше (!) line-box так, как показано на картинке. Я хотел бы напомнить вам, что высота line-box вычисляется от самой высокой точки своего дочернего элемента к низшей точке своего дочернего элемента.

Меньший дочерний элемент может привести к увеличении высоты line-box

Меньший дочерний элемент может привести к увеличении высоты line-box

Это может быть аргументом в качестве использования безразмерного значения line-height, но иногда необходимо его фиксировать, чтобы создать идеальный вертикальный размер. Если честно, то независимо от того, что вы выберете, вы всегда будете иметь проблемы с inline выравниванием.

Рассмотрим еще один пример. Тег <p> с line-height: 200px, содержащий один <span>, унаследовал значение line-height

<p>
    <span>Ba</span>
</p>
p {
    line-height: 200px;
}
span {
    font-family: Catamaran;
    font-size: 100px;
}

Насколько высок line-box? Следует ожидать, что 200px, но это не так. Проблема здесь заключается в том, что <p> имеет свой собственный font-family (по умолчанию serif). Базовые линии тега <p> и <span> скорее всего, будут различны, а высота line-box выше, чем ожидалось. Это происходит потому, что браузеры производят вычисления следующим образом: так, как если бы каждый line-box начинался бы с нулевой ширины символа.

Невидим, но заметен.

В итоге, мы столкнулись с той же проблемой, что и в предыдущем примере.

Каждый дочерний элемент выравнивается так, как если бы его line-box появился бы с невидимой нулевой ширины символа

Каждый дочерний элемент выравнивается так, как если бы его line-box появился бы с невидимой нулевой ширины символа

Базовая линия скачен, но что можно сказать на счетvertical-align: middle? Как пишут в спецификации, middle “выравнивает вертикальную среднюю точку контейнера с базовой линией родительского контейнера плюс половина x-height родительского блока”. Соотношение базовых линий различны, а также  как различно соотношение x-height, следовательно выравнивание middle не надежно. Как правило, middle никогда не бывает по “середине”. Этому способствуют много факторов в CSS (x-height, ascender/descender соотношение, и тд.)

В качестве примечания:

  • vertical-align: top / bottom выравнивает по верху и низу line-box
  • vertical-align: text-top / text-bottom выравнивает по верху и низу content-area
Vertical-align: top, bottom, text-top и text-bottom

Vertical-align: top, bottom, text-top и text-bottom

Но используйте с осторожностью, это выравнивает virtual-area. Посмотрите это на простом примере, используя vertical-align: top. Невидимый line-height может привести к странным, но не удивительным, результатам.

vertical-align может привести к нечетным результат на первых порах, но предполагается, что line-height создаст визуализацию

vertical-align может привести к нечетным результат на первых порах, но предполагается, что line-height создаст визуализацию

И в заключении стоит отметить, что vertical-align также принимает числовые значения, которые повышают или понижают поле относительно базовой линии.

CSS превосходен

Мы поговорили о том, как line-height и vertical-align работают вместе, но теперь вопрос: можно ли управлять показателями шрифта с помощью CSS? Короткий ответ: нет.

Что делать, если, например, мы хотим, чтобы текст с помощью шрифта Catamaran, где высота заглавной буквы 100px стал выше? Давайте немного займемся математикой.

Сначала мы устанавливим все показатели шрифта в качестве CSS-свойств, а затем вычислим font-size,чтобы получить высоту заглавной буквы 100px.

p {
    /* font metrics */
    --font: Catamaran;
    --capitalHeight: 0.68;
    --descender: 0.54;
    --ascender: 1.1;
    --linegap: 0;

    /* desired font-size for capital height */
    --fontSize: 100;

    /* apply font-family */
    font-family: var(--font);

    /* compute font-size to get capital height equal desired font-size */
    --computedFontSize: (var(--fontSize) / var(--capitalHeight));
    font-size: calc(var(--computedFontSize) * 1px);
}
Высота заглавной буквы - 100px

Высота заглавной буквы — 100px

Довольно просто, не так ли? Но что, если мы хотим, чтобы текст визуально стоял по середине? Так чтобы оставшееся пространство равномерно распределялось по верхней и нижней части буквы «B»? Для достижения этой цели, мы должны вычислить vertical-alignбазирующийся на соотношении ascender/descender.

Во-первых, установим line-height: normal и высоту content-area:

p {--lineheightNormal: (var(--ascender) + var(--descender) + var(--linegap));
    --contentArea: (var(--lineheightNormal) * var(--computedFontSize));
}

Затем нам нужно установить:

  • расстояние от нижней части буквы к нижнему краю
  • расстояние от верхней части буквы к верхнему краю

Вот так:

p {--distanceBottom: (var(--descender));
    --distanceTop: (var(--ascender) - var(--capitalHeight));
}

Теперь мы можем вычислить vertical-align, которая представляет собой разность между расстояниями, умноженное на величину font-size. (мы должны применить это значение для дочернего элемента inline)

p {--valign: ((var(--distanceBottom) - var(--distanceTop)) * var(--computedFontSize));
}
span {
    vertical-align: calc(var(--valign) * -1px);
}

В конце концов, мы устанавливаем желаемый line-height и вычисляем его при сохранении вертикального выравнивания:

p {/* desired line-height */
    --lineheight: 3;
    line-height: calc(((var(--lineheight) * var(--fontSize)) - var(--valign)) * 1px);
}
Результат установки для line-height разных значений. Текст всегда располагается по середине.

Результат установки для line-height разных значений. Текст всегда располагается по середине.

Добавить значок, высота которого соответствует букве «B», теперь легко:

span::before {
    content: '';
    display: inline-block;
    width: calc(1px * var(--fontSize));
    height: calc(1px * var(--fontSize));
    margin-right: 10px;
    background: url('https://cdn.pbrd.co/images/yBAKn5bbv.png');
    background-size: cover;
}

 

Иконка и буква B имеют одинаковую высоту

Иконка и буква B имеют одинаковую высоту

Посмотрите результат на JSBin

Обратите внимание, что это демо предназначено только для демонстрационных целей. Почему? На это есть много причин:

  • показатели шрифта могут быть не постоянными ¯⁠\⁠(ツ)⁠/⁠¯
  • если шрифт не загружен, то резервный шрифт, верноятно, будет иметь различные показатели шрифта и иметь дело с несколькими значениями, а значит — станет не управляемым.

Выводы

  • inline форматирование текста действительно трудно понять;
  • все inline-элементы имеют 2 высоты:
    • контент-области, иначе content-area (данные берутся из показателей шрифта)
    • виртуальная область, иначе virtual-area (line-height)
    • ни один из этих 2-х высот не может быть визуализирован.
  • line-height: normal основан на показателях шрифта
  • line-height: normal может создать virtual-area меньше, чем content-area
  • vertical-align не очень надёжен
  • высота line-box вычисляется на основе своих дочерних элементов со свойствами line-height и vertical-align
  • мы не можем легко получить / установить показатели шрифта при помощью CSS
  • есть спецификация, чтобы помочь с вертикальным выравниванием: Line Grid module

Но я до сих пор люблю CSS 🙂

Ресурсы

 

 

Перевод статьи «Deep dive CSS: font metrics, line-height and vertical-align»

Предыдущий пост

Три новые функции в CSS для изучения в 2017 году

Следующий пост

Преимущества макетов AI для верстальщиков

Комментарии

  • Сергей

    Классная статья! Молодец!

    Reply to Сергей
  • NikW

    Чтобы легко понять законы по которым живёт текст в вебе, нужно понять базовые принципы типографики и устройства шрифта. После этого описанные в статье CSS-свойства и прочие понятия начинают становятся ясными и логичными, а некоторые шрифты больше никогда не используются — ибо сделаны через задницу и именно поэтому не становятся как надо.

    Очень рекомендую всем кто хочет перейти с текстом на «ты» прочесть две книги: Д. Феличи — Типографика, шрифт, вёрстка, дизайн и Р. Брингхерст — Основы стиля в типографике.

    Reply to NikW
  • css-monster

    Спасибо! Не знал, что так можно делать…

    Reply to css-monster

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *