CSS-live.ru

Неизведанные глубины CSS: метрики шрифта, line-height и vertical-align

Перевод статьи Deep dive CSS: font metrics, line-height and vertical-align с сайта iamvdo.me, опубликовано на css-live.ru с разрешения автора — Венсана де Оливейры.

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

Например, line-height можно задать в виде длины или безразмерного значения 1, но по умолчанию у него значение 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> с 3 <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-size, разная высота в итоге

Даже если мы в курсе этой особенности, почему font-size: 100px не делает элементы высотой 100px? Я измерил эти значения: Helvetica — 115px, Gruppo — 97px и Catamaran — 164px.


Высота элементов с font-size: 100px колеблется от 97px до 164px

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

  • шрифт задает свои единицы измерения em-квадрата (или UPM, units per em — единиц на кегль), некий контейнер, в котором будут рисоваться все символы. В этом контейнере всё измеряется в относительных единицах и обычно он принимается за 1000 единиц. Но бывает и 1024, и 2048, и сколько угодно.
  • на базе этих относительных единиц задаются метрики шрифта: высота верхних выносных элементов (ascender), нижних выносных (descender), заглавных букв (capital height), строчных букв (x-height) и т.п. Заметьте, что некоторые значения могут выступать за рамки em-квадрата.
  • в браузере эти относительные единицы масштабируются до заданного font-size.

Возьмем шрифт Catamaran и откроем его в FontForge, чтобы увидеть метрики:

  • em-квадрат принят за 1000 единиц
  • высота верхних выносных — 1100, а нижних — 540. Судя по нескольким тестам, браузеры используют значения HHead Ascent/Descent на Mac OS и Win Ascent/Descent на Windows (и эти значения могут различаться!). Также видно, что высота заглавных букв (Capital Height) равна 680, а высота строчных (X height) — 485.


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

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

Также легко предсказать, что высота заглавных букв будет 68px (680 единиц), а строчных (x-высота) — 49px (485 единиц). Как результат, 1ex = 49px, а 1em = 100px, а не 164px (к счастью, em отсчитывается от font-size, а не от вычисленной высоты)


Шрифт Catamaran: единицы на em (UPM) и их эквиваленты в пикселях при font-size: 100px

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

Каждый HTML-элемент на самом деле представляет собой «стопку» контейнеров строки. Если вы знаете высоту каждого контейнера строки, вы знаете и высоту элемента.

Если мы изменим предыдущий 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>

Он сгенерирует 3 контейнера строки:

  • в первом и последнем — только по одному анонимному элементу строки (текст)
  • во втором — два анонимных элемента строки и 3 <span>


Элемент <p> (черная рамка) состоит из контейнеров строк (белые рамки), состоящих из строчных элементов (сплошные рамки) и анонимных элементов строки (пунктирные рамки)

Хорошо видно, что второй контейнер строки выше остальных, из-за вычисленной области содержимого его потомков, а точнее — того, что со шрифтом Catamaran.

Самое сложное в создании контейнера строки то, что мы не можем ни толком это увидеть, ни управлять этим из CSS. Даже задание фона для ::first-line не помогает представить высоту первого контейнера строки наглядно.

line-height: до подводных камней и дальше

До сих пор я ввел два понятия: область содержимого и контейнер строки. Если вы читали внимательно, я сказал, что высота контейнера строки рассчитывается по высоте его потомков, я не говорил «высоте области содержимого потомков». И в этом большая разница.

Хоть это может прозвучать странно, у элемента строки есть две разных высоты: высота области содержимого и высота виртуальной области (я придумал термин «виртуальная область», потому что эта высота нам не видна, но в спецификации вы нигде этого термина не найдете).

  • высота области содержимого определяется метриками шрифта (как мы видели раньше)
  • высота виртуальной области — это line-height, и именно эта высота используется при расчете высоты контейнера строки


У элементов строки есть две разные высоты

Помимо прочего, это опровергает популярное представление, что line-height — это расстояние между базовыми линиями шрифта. В CSS это не так 3.


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

Вычисленная разность высот между виртуальной областью и областью содержимого называется «leading», или интерлиньяж (прим. перев.: в некоторых русскоязычных источниках «интерлиньяжем» называют саму «высоту виртуальной области» в терминах Венсана, что часто приводит к путанице. Интересно, что во французской версии статьи Венсан использует тоже англоязычный термин «leading»). Половина интерлиньяжа добавляется сверху от области содержимого, вторая половина снизу от нее. Таким образом, область содержимого всегда будет посередине виртуальной области.

В зависимости от вычисленного значения, line-height (виртуальная область) может быть равна, выше или ниже области содержимого. Если виртуальная область ниже, то интерлиньяж отрицательный и контейнер строки визуально оказывается ниже собственных потомков.

Есть и другие виды элементов строки:

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

Для этих особых строчных элементов высота рассчитывается по их свойствам height, margin and border. Если у height значение auto, то используется line-height, и высота области содержимого строго равна line-height.


У замещаемых строчных элементов, элементов типа inline-block/inline-* и «блокифицированных» строчных элементов высота области содержимого равна их высоте, или line-height.

И всё же мы пока так и не приблизились к разгадке, чему же равно значение normal у line-height? И ответ, как и при расчете высоты области содержимого, надо искать в метриках шрифта.

Вернемся в FontForge. Размер em-квадрата для шрифта Catamaran равен 1000, но мы видим много других значений для верхних/нижних выносных и не только:

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

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

Для сравнения, в шрифте Arial em-квадрат взят за 2048 единиц, высота верхней части — 1854, нижней части — 434, а межстрочный зазор — 67. Это значит, что при font-size: 100px высота области содержимого будет 112px (2288/2048≈1,117), а line-height: normal — 115 px (2355/2048≈1.15). Все эти метрики специфичны для каждого шрифта и заданы дизайнером-шрифтовиком, который его создал.

Становится очевидно, что указывать line-height: 1 — плохой подход. Напоминаю, что безразмерные значения отсчитываются от font-size, а не от области содержимого, и немало проблем возникает от того, что виртуальная область оказывается меньше области содержимого.


Из-за line-height: 1 контейнер строки может стать ниже, чем область содержимого

Но не только line-height: 1. Например, из 1117 шрифтов, установленных на моем компьютере (да, я установил все шрифты из Google Web Fonts) у 1059, это около 95%, вычисленная line-height больше 1. Вообще вычисленная line-height у них колеблется от 0.618 до 3.378. Вам не померещилось, 3.378!

Немного подробностей о расчете контейнера строки:

  • для строчных элементов padding и border увеличивают область фона, но не область содержимого (и не высоту контейнера строки). Так что не всегда область содержимого — то, что видно на экране. А margin-top и margin-bottom ни на что не влияют.
  • для замещаемых строчных элементов, элементов типа inline-block и «блокифицированных» строчных элементов: padding, margin и border увеличивают height, а значит, и высоту области содержимого и контейнера строки.

vertical-align: одно свойство, чтоб править всеми

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

По умолчанию у него значение baseline. Помните метрики шрифта «ascender» и «descender» (верхняя и нижняя части)? Они определяют, где находится базовая линия, т.е. в какой пропорции она делит символ на верхнюю и нижнюю части (как ватерлиния корабля делит его на надводную и подводную части — прим. перев.). Поскольку пропорция между верхней и нижней часть редко бывает 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. Базовые линии совпадают и высота контейнера строки равна их line-height.


Тот же шрифт, та же базовая линия, на вид всё в порядке

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

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

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


Меньший дочерний элемент может привести к большей высоте контейнера строки

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

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

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

Какой высоты будет контейнер строки? Можно подумать, что 200px, но не тут-то было. Проблема в том, что у этого <p> своё, другое значение font-family (по умолчанию оно serif). Положение базовой линии у <p> и у <span> наверняка разное, поэтому высота контейнера строки больше, чем ожидалось. Это потому, что браузеры считают так, как будто каждый контейнер строки начинается с символа нулевой ширины, который спецификация называет термином «strut».

Символ невидимый, но влияние заметное

Итого, перед нами та же проблема, что в прошлом примере с соседними элементами.


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

Выравнивание по базовой линии пролетает, но, может быть, vertical-align: middle нас спасет? Как гласит спецификация, значение middle «выравнивает вертикальную середину бокса с базовой линией родительского бокса плюс половина родительской x-высоты». Положение базовых линий бывает разное, x-высота — тоже, так что и на middle положиться нельзя. Хуже всего то, что в большинстве сценариев middle никогда не бывает по-настоящему «посередине». Слишком много всего влияет и это нельзя прописать в CSS (x-высота, относительное положение базовой линии и т.п.)

Отмечу, что есть еще 4 значения, полезные в некоторых случаях:

  • vertical-align: top / bottom выравнивают по верху или низу контейнера строки
  • vertical-align: text-top / text-bottom выравнивают по верху или низу области содержимого


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

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


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

Наконец, vertical-align принимает также числовые значения, которые поднимают или опускают бокс относительно базовой линии. Этот последний вариант часто может выручить.

«CSS is awesome»

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

Что если, например, нам нужен текст шрифтом Catamaran, в котором заглавные буквы будут ровно 100px высотой? С виду осуществимо: давайте немного посчитаем.

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

p {
    /* метрики шрифта */
    --font: Catamaran;
    --capitalHeight: 0.68;
    --descender: 0.54;
    --ascender: 1.1;
    --linegap: 0;

    /* нужная высота заглавных букв */
    --fontSize: 100;

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

    /* рассчитываем font-size для получения нужной высоты заглавных букв */
    --computedFontSize: (var(--fontSize) / var(--capitalHeight));
    font-size: calc(var(--computedFontSize) * 1px);
}


Теперь заглавные буквы высотой 100px

Достаточно очевидно, правда? Но что если мы  хотим вертикально отцентрировать текст, чтобы свободное место поровну распределялось сверху и снизу от буквы «B»? Чтобы добиться этого, придется рассчитать vertical-align на основе положения базовой линии (пропорции верхней/нижней частей).

Сначала рассчитаем величину line-height: normal и высоту области содержимого:

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 (нам надо будет применить это значение к строчному дочернему элементу).

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

И в конце зададим желаемую высоту строки и вычислим соответствующий line-height так, чтобы вертикальное выравнивание сохранилось:

p {
    …
    /* желаемая высота строки */
    --lineheight: 3;
    line-height: calc(((var(--lineheight) * var(--fontSize)) - var(--valign)) * 1px);
}


Результаты для разных 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» равны по высоте

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

Учтите, что этот тест только для демонстрационных целей. Полагаться на это нельзя. Причин много:

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

В сухом остатке

Что мы узнали:

  • понять строчный (инлайновый) контекст форматирования нелегко
  • у всех строчных элементов 2 высоты
    • область содержимого (основанная на метриках шрифта)
    • виртуальная область (line-height)
    • ни ту, ни другую высоту нельзя однозначно визуализировать (если вы разрабатываете браузерный отладчик и готовы взяться за это — было бы великолепно!)
  • line-height: normal основывается на метриках шрифта
  • при line-height: n виртуальная область может стать меньше, чем область содержимого
  • на vertical-align нельзя особо полагаться
  • высота контейнера строки рассчитывается на основе свойств line-height и vertical-align его потомков
  • нельзя просто взять и получить/установить метрики шрифта из CSS
  • есть в планах спецификация на эту тему, чтобы помочь с вертикальным выравниванием: модуль строчной сетки (Line Grid)

Но я по-прежнему люблю CSS :)

Полезные ссылки

  1. неважно, что вы выберете, здесь речь не об этом
  2. это не совсем так
  3. в других редакторах это может быть и расстояние между базовыми линиями. В Ворде, в Фотошопе это так. Главное отличие в том, что в CSS оно влияет и на первую строку
  4. можно использовать и переменные из препроцессоров, не в пользовательских свойствах суть

P.S. Это тоже может быть интересно:

30 комментариев

    1. И что? Получилось разобраться? Я не смог. На мой субъективный взгляд статья переведена посредственно.

      1. Материал объективно непростой, но если есть предложения, как можно сделать его понятнее – давайте попробуем его улучшить!

  1. Элемент (черная рамка) состоит из контейнеров строк (белые рамки), состоящих из строчных элементов (сплошные рамки) и анонимных элементов строки (белые рамки)

    Видимо ошибка: и анонимных элементов строки (пунктирные рамки).

  2. По переводу: мне кажется в некоторых случаях переводите слишком буквально. По-русски фраза «В этом большая разница» будет звучать: «И в этом большая разница!» (хотя вообще, наверное, в зависимости от контекста). Аналогично, фраза «И в этом большая разница!» на английском будет переведена, вероятно, без And. И таких моментов может быть много (я не знаю, удачный ли я привёл пример). Пусть перевод будет чуть более вольный, но, при этом, не звучит нигде натянуто, если для смысла это не критично. Авторы привыкли говорить так, как принято там, где они общаются, но мысли их могут быть понятны и нам :-) Если типичные для слушателя обороты речи, смогут более естественно передать ОРИГИНАЛЬНОСТЬ АВТОРСКОЙ МЫСЛИ, то может лучше использовать их?
    Напоминаю, что высота контейнера строки рассчитывается от самой высокой до самой низкой точки его потомков.
    Тут ещё хорошо бы напомнить, что область содержимого находится ПОСЕРЕДИНЕ line-height. При первом прочтении это не очевидно :-)
    А вообще статья просто превосходна! Очень порадовала!..
    Странно, а почему Вы не даёте ссылку на свою книжку? Скромничаете?
    По-сути в этой статье разъяснены некоторые моменты, которые затрагиваются в Вашей книжке. Статья здорово её дополняет, развевает туман в некоторых местах, например вносит практически полную ясность в базовое понятие «высота строки», на котором многое строится и не понимая которое, очень сложно воспринять те или иные аспекты строчного форматирования и разобраться как же именно они работают…
    ЗЫ: По поводу рассчётов в CSS тоже тут недавно вёл рассчёты (в Маткаде), чтоб понять откуда-докуда заливать градиент у траснформируемой фигуры. Не то, что это плохо само по себе, но всё же, в некоторых аспектах, пожалуй, несколько запутанно. Думаю, что некоторые дополнительные возможности (не упрощения!) сделали бы реализацию тех или иных вполне интуитивных задач более простой и надёжной. Вот как раз:
    ЗЫЗЫ: Собираетесь ли Вы вносить в спецификацию, какие-то свои предложения, по строчному форматированию? :-)

    1. в некоторых случаях переводите слишком буквально

      Возможно. Слегка торопился успеть опубликовать перевод до намечавшегося на работе аврала, пытался сэкономить время на втором проходе вычитки… эх, зря. Сейчас вот еще немного подправил пару мест. Возможно, сложности добавляет еще то, что для самого автора английский не родной, в нескольких местах понять его мысль помогла лишь французская версия статьи:)

      ещё хорошо бы напомнить, что область содержимого находится ПОСЕРЕДИНЕ line-height.

      Не хотелось добавлять слишком много отсебятины переводчика. Чтобы не пришлось в итоге перечислять всё, из чего складывается расстояние от базовой линии до нижнего края «виртуальной области» (удачный авторский термин, кстати). Но спасибо за замечание, возможно, кому-то этот комментарий поможет быстрее разобраться!

      По-сути в этой статье разъяснены некоторые моменты, которые затрагиваются в Вашей книжке. Статья здорово её дополняет

      Именно поэтому я так спешил ее перевести:). Свежий взгляд на знакомую тему, причем за это время браузеры избавились от многих разночтений, более-менее удалось «разрулить» путаницу с переводом многозначных терминов типа «inline», и т.п.

      Предложений в спеку пока не вносил, но поддерживал некоторые чужие, например, сделать высоту заглавных букв новой единицей измерения (по аналогии с существующей ex).


      1. Слегка торопился успеть опубликовать перевод

        Да в целом-то нормально, я просто обратил внимание, что когда читаешь переводные статьи то иногда сталкиваешь с такой конструкцией фраз, что понимаешь, что автор — англоговорящий (в данном случае даже француз %)


        до намечавшегося на работе аврала

        Эх, где взять хвалёную в Вашей статье Путь верстальщика «программистскую мудрость», чтоб знать: когда капать, а когда не капать :-))


        Чтобы не пришлось в итоге перечислять всё, из чего складывается расстояние от базовой линии до нижнего края «виртуальной области»
        </cite

        Сложно сказать нужно это или нет, но могу сказать одно, когда разбираешься в чём-то сложном, мысленно раскладываешь его на (относительно) простые компоненты, о которых читал ранее… И тут иногда очень здорово проверить, что твои мысли сошлись с авторскими :-)
        Иначе потом совсем можно потеряться…

        «виртуальной области» (удачный авторский термин, кстати).
        Насколько мне известно, слово «виртуальный» в английском языке имеет немного более ёмкое значение чем в русском. Если в русском это что-то вроде «воображаемый», «не существующий» «предполагаемый в уме» ну в лучшем случае «где-то в параллельном мире», то в английском, это что-то вроде «непроявленное». То есть, в явном виде может и не существующее, но о чём-то говорящее… Вот и line-height, с одной стороны его увидеть не получается, но его параметры дают другим значениям найти своё конкретное и вполне законное место в строке.


        спасибо за замечание, возможно, кому-то этот комментарий поможет быстрее разобраться!

        С этой целью, в основном и написал, об этом сказано в начале статьи, а в примере, обратили внимание, на довольно очевидную вещь, что мол строка складывается от самого высокого места, до самого низкого, а почему виртуальная область распределена именно так — не написано.


        поддерживал некоторые чужие, например, сделать высоту заглавных букв

        Да, большая тема…
        Спасибо!

  3. vertical-align: text-top / text-bottom выравнивают по верху или низу области содержимого
    Но будьте осторожны: во всех случаях оно выравнивает виртуальную область, высота которой невидима
    Так что к чему выравнивается? Область содержимого к области содержимого или виртуальную область, выравниваемого элемента, к области содержимого strut? (или даже не strut?! а, скажем, самого высокого элемента в строке? врят ли, ну а вдруг? :-) )

    1. Насколько я понимаю, виртуальную область выравниваемого элемента к родительской области содержимого (если у выравниваемого элемента есть инлайновый родитель — то к области содержимого этого родителя, если нет — к области содержимого strut родительского блока).

  4. Учтите, что этот тест только для демонстрационных целей. Полагаться на это нельзя. Причин много:
    метрики шрифтов-то постоянны, но вот браузерные вычисления — не очень ¯⁠\⁠(ツ)⁠/⁠¯
    если шрифт не загрузился, у резервного шрифта скорее всего другие метрики, а работать с несколькими разными значениями быстро станет практически нереально.

    Интересную ссылку приводит автор (span-область наск. я понимаю, Content-Area). Посмотрев значения шрифтов в указанной автором программе, оказалось, что поймать вертикальный ритм не так просто. И у меня получилось сделать это в Хроме для размера шрифта, при котором размер его заглавных букв составил 200px (А вот при размере заглавных 120px ритм съезжал). Вот видимо почему — браузер округляет значения, в результате точное значение — не то значение, которое используется по факту. Кстати, в этой таблице нет размера заглавных, но наверняка используются тоже какие-то целые значения.
    Интересно что в Хроме всё работает для двух шрифтов, при этом ощущение, что для Times шрифтовая площадка, всё же сдвинута на 1px вверх, и только для Arial всё нормально. При этом в Лисе всё постепенно съезжает…

    Чтобы не пришлось в итоге перечислять всё, из чего складывается расстояние от базовой линии до нижнего края «виртуальной области»

    можно воспользоваться вот этой шпаргалкой (изображение кликабельно). Но, что бросается в глаза: некоторые отличия литерных площадок от виртуальных. Во-первых em-квадрат и line-height теперь разные вещи, одно строится в пространстве другого(или другое строится в координатах первого), но теперь это разные понятия связанные друг с другом лишь относительно.
    И второе, следующее из первого, line-height теперь не меряется как расстояние между базовыми линиями, а, насколько я понимаю, от верхнего half-leading до нижнего (или наоборот, без разницы). При этом сам leading стал понятием условным, лидинги напрямую друг с другом не контактируют, а контактируют line-box (хотя понятно, что самая верхняя и самая нижняя точка line-box это чей-то half-leading, причём, я так понимаю, это верно, даже если в строке есть замещаемые элементы или inline-block, поскольку, из чего бы они не состояли, они лежат в области содержимого, а только халф-лидинги шрифтов образуют межстрочное расстояние, которое отделяет содержимое одного лайн-бокса, от другого).


    1. причём, я так понимаю, это верно, даже если в строке есть замещаемые элементы или inline-block

      Похоже что путаю: и тем и другим плевать на лидинги(кроме возможно, случае vertical-align text-top и text-bottom), они видят контейнер (строка — лайнбокс) и укладываются в него как им удобнее :) Если такой элемент самый высокий в строке (или vertical-align указан как top или bottom), то замечательно умещаются до краёв виртуальной области строки.

  5. Тут такой вопрос возник: как связаны безразмерное значение line-height и значение, в единицах измерения?
    Я считал, что line-height: 1 это тоже самое что 1em. То есть, если размер font-size: 16px, то,
    если мы зададим line-height: 1 то он будет равен 16px.
    Но тут неожиданно оказалось что значения безразмерного коэффициента, и это же значение, умноженное на font-size, незначительно различающиеся значения.
    Кто-нибудь может мне объяснить, в чём тут дело?

    1. В первом приближении оно и должно быть равно 1em. Могут быть разве что нюансы до пикселя из-за округления. Можно увидеть пример различия?

      Разница между line-height: 1 и line-height: 1em в том, как они наследуются: для размерных величин наследуется то значение в пикселях, которое получается для родительского элемента, а для безразмерных — сам коэффициент, который для каждого дочернего элемента заново умножается на его font-size.


      1. В первом приближении оно и должно быть равно 1em. Могут быть разве что нюансы до пикселя из-за округления. Можно увидеть пример различия?

        Да, вот пример. Голову сломал — почему ритм не сходится? Всё 10 раз перепроверил, и неожиданно выяснилось, что если поменять значение line-height на размерное, то всё сходится(43-44 строчки).
        Соответственно, возник вопрос — почему?


        Разница между line-height: 1 и line-height: 1em в том, как они наследуются: для размерных величин наследуется то значение в пикселях, которое получается для родительского элемента, а для безразмерных — сам коэффициент, который для каждого дочернего элемента заново умножается на его font-size.

        Ну, в приведённом примере, лайн-бокс, насколько я понимаю, формируют strut, который либо умножаешь на фонт-сайз, либо оставляешь безразмерным. У дочернего span line-height задано как normal, который заведомо меньше strut, а большОй белый элемент справа — абсолютно позиционирован, соответственно на размер родителя(а соответственно и размер строки) влиять не должен.
        Может, конечно, я где-то что-то проворонил и дело не в этом, но открытием было именно то, что когда умножаешь безразмерное значение на фонт-сайз, ритм выстраивается…

  6. Как я уже говорил с большим удовольствием прочитал статью, и выяснил для себя, некоторые пробелы, которые у меня, к сожалению, остались после прочтения Вашей книжки.
    Автор очень подробно рассказывает о том, что же действительно происходит в строке, и, возникает ощущение, что теперь, я примерно понимаю, что же действительно происходит в строке – по крайней мере что касается, вертикальной составляющей, а она, насколько я понимаю, в вебе, основная.
    Кстати, в примере, приведённом в статье, ContentArea (область содержимого), похоже, названо тем, что, по-идее, является виртуальной областью (правда у разобранного автором шрифта значение line-gap(межстрочного расстояния) равно нулю, а значит область контента и область содержимого совпадают). Но у других шрифтов может быть по-другому.
    Тем не менее, обстоятельно рассказав теорию, пример, на мой взгляд, автор пояснил немного бегло. Причём общая идея примера, достаточно прозрачна, но всё же, практика показывает, что есть нюансы. И, если интересно, не только понять о чём речь, но и увидеть, что с чем будет взаимодействовать, и какие возникают пути решения, то остаются вопросы.
    Попробуем разобраться, что в принципе происходит в строке такого, что поможет нам увидеть вертикальный ритм в строке и понять как работает выравнивание.
    На мой взгляд, если попытаться выразить идею вертикального выравнивания одним словом, то слово это будет «относительное». Строки заполняют пространство документа с верху вниз, соответственно каждая строка, своим верхним краем, упирается в верхнюю строку (или начало документа), располагая своё содержимое вниз, расширяясь, при этом, с тем, чтоб уместились все её элементы, учитывая их вертикальное расположение, друг относительно друга. Элемент, расположенный в строке выше всех, упирается своей верхней частью, в верхнюю часть строки и чем выше он расположен, тем больше пространство строки увеличится вниз, а значит сместит вниз и находящиеся в ней элементы. Таким образом, элемент может поменять своё положение не только изменив собственное значение vertical-align, но и под влиянием соседей. Значение вертикального выравнивания показывает положение элемента в строке относительно других элементов строки.
    В блоке есть текстовый потомок по-умолчанию, называется strut (про который сказано и в статье и в книжке). Мало того, что он передаёт по-наследству свои строчные параметры, так он ещё и незримо присутствует в строках блока, обуславливая, тем самым её минимальный размер. Насколько мне известно, избавится от его влияния можно только задав блок-обёртку с другими параметрами. Для эксперимента, можете сделать блок со свойством line-height: 100px, и попробовать сделать в любом месте блока расстояние между строками размером, скажем, 16px, используя только строчное форматирование. Как сделаете, мне обязательно расскажите :-)
    Влияние этого блочного потомка, strut, обязательно нужно учитывать. Он может присутствовать незримо и совпадать со своим видимым коллегой, особенно если коллега безымянный и пользуется по-умолчанию тем, что ему досталось от этого strut, но влияние strut неожиданно может проявиться. Получается, в заполненной строке, УЖЕ есть два элемента: текущий и strut.
    А поскольку выравнивание, во многом, относительное, то либо элемент выравнивается, относительно strut, либо strut относительно элемента. Вот – основной теоретический каркас, на котором строится всё выравнивание. Стоит ещё отметить что для strut напрямую задать вертикальное выравнивание нельзя, его расположение – некая блочная базовая линия, положение которой, впрочем, может меняться под влиянием других элементов в строке.
    Продолжу в следующем комментарии…

  7. Теперь давайте посмотрим на задачу, предложенную в статье: сделать высоту строки, пропорциональной заглавным буквам определённого шрифта, при этом, сами заглавные буквы, должны находится ровно посередине строки.
    Первое, что приходит в голову, это сделать strut большим (задав большой line-height для содержащего блока), а элемент двигать относительно него. Такой подход имеет очевидный недостаток – все строки в блоки будут далеко друг от друга. Если блок для этого и создан, то, как говорится – нет проблем, а если это общий контейнер, где каким-то элементам нужно придать определённые свойства, то это может мешать. Рецепт лечения такой бяды понятен – блок-обёртка (либо для участков где нужна большая строка, либо для участков, где строка нужна маленькая, нивелируя влияния большой на маленькую), насколько он оправдан в каждом конкретном случае – зависит от того, насколько мы не хотим загружать html проблемами CSS :-)
    Известно, что область содержимого лежит посередине виртуальной области, а значит если в области содержимого, заглавные буквы будет находится посередине, то они будут находится и посередине строки. Параметры шрифта посмотрим, в указанной в статье программе FontForge, расстояния от края области содержимого до ближнего края заглавных букв посчитаем по примеру в статье (от низа – расстояние нижних выносных элементов, верх – разница между верхними выносными элементами и заглавной буквой).
    Сравним эти расстояния – какое-то из них, вероятно будет чуть больше. Значит нужно сдвинуть в противоположную сторону настолько, чтоб эти расстояния выровнялись. Посчитаем разницу между этими расстояниями, половина разницы – будет посередине. Значит если мы сдвинем на это расстояние, с одной стороны прибавится, в с другой, настолько же, убавится, и буква выровняется по центру. Замечу, что двигать букву, относительно её базовой линии (и соответственно остальных строчных параметров) средствами CSS не получится. Но на деле, мы двигаем букву относительно strut, который, в виду своих больших размеров, и формирует строку.
    Итак, проба для шрифта Times New Roman, и для шрифта Arial. В Хроме и Лисе всё работает – смотрим вертикальный ритм, еле уловимый, словно шум моря из ракушки. Похоже, для Arial в Chrome ритм не идеален.
    Здесь и далее, смотрите примечания к коду.

  8. Второй вариант, логично напрашивающийся, это сделать большой span и маленький strut. Но, как не сложно догадаться, сделать его значительно сложнее – относительно чего будет двигаться наше строчное содержимое, если оно и есть самый большой элемент в строке? Скорее, всё остальное (в том числе и strut) будет двигаться, и только потом сам элемент. Поэтому случай с маленький strut мы оставим на закуску, а займёмся вторым вариантом (который, кстати, похоже и был разобран в статье).
    Принцип взаимодействия этих элементов отличается: если в случае, когда strut был большим, то только он принимал участие в формировании строки, span же целиком помещался в его пространстве, то теперь оба элемента имеют влияние на высоту строки. И, если мы изменим положение одного, относительно другого, то элемент, оказавшийся выше, будет упираться в верх строки, а ниже – в её низ. Высота строки же вырастет на величину сдвига.
    По-идее, если расстояние от верхнего края заглавной, до края области содержимого, чуть больше чем аналогичное расстояние внизу, то уровнять мы можем либо немного уменьшив расстояние вверху, либо немного увеличив расстояние внизу. Уменьшая или увеличивая line-height, мы обрежем или дополним элемент с двух сторон, при этом разница, между расстоянием до верха и до низа, никуда не денется – и её потребуется чем-то скомпенсировать. Чем же?
    Итак, у нас есть два расстояния от заглавной буквы, до верха и до низа строки. Находящиеся на одном уровне два элемента одинаковый высоты, занимают минимальную возможную для них высоту, и любой сдвиг одного из них, приведёт к увеличению строки. Для того, чтоб меньшее расстояние стало равным большему, нужно сдвинуть второй элемент в сторону увеличиваемой стороны, на расстояние этого увеличения (второй элемент, при этом, играет, своеобразную роль заглушки). Соответственно, размер этого сдвига, равен разнице между меньшим и большим расстоянием (от верхнего края буквы или от нижнего края, буквы до ближайшего для него края строки). Что же это за второй элемент? – strut.
    Понятно, что удобней двигать (воздействовать на) сам элемент, поскольку strut затрагивает всё содержимое блока, а элемент, только самого себя и влияет только на ту строку, в которой лежит. Как мы помним, вертикальное выравнивание относительно, а значит, для того, чтоб сдвинуть strut, например, вниз, мы можем поднять элемент вверх. При этом, поскольку элементы одинаковые, строка увеличится на величину сдвига.
    Единственная сложность, в данном случае, что размер строки нужно привести к размеру заглавных букв. Если в предыдущем примере, сделать это было относительно легко – мы просто увеличивали strut (предварительно посчитав его размер в заглавных буквах), то теперь, на размер строки влияют два элемента, каждый из которых чуть-чуть меньше полного размера строки. Но, поскольку, понятно, насколько они меньше (на величину сдвига), то высчитать зависимость наших элементов от величины заглавных не составит большого труда. В общем, этот способ не намного сложнее, и, тем не менее, для его успешной реализации, пришлось переписать большую часть кода, сделав алгоритм расчёта, по возможности, более прозрачным.
    В данном случае, кстати, приходится «вручную» смотреть какое расстояние больше – до верха или до низа. Благо считается это просто, да и в коде результат нужно вставить всего в паре мест, но всё-таки, пожалуй, небольшое неудобство. Отмечу, что размер сдвига, по сравнению с предыдущим примером, в два раза больше: здесь разница, между расстоянием от заглавной до верха, и до низа, а в предыдущем – лишь половина разницы.
    Вот результат, для Arial и для Times New Roman. Хром-Лиса полёт нормальный :)

  9. Ну и, наконец, третий случай, strut маленький, а элемент большой. Как быть? Решение простое, как дрова — псевдоэлемент :-) Он берёт на себя функции второго элемента (равной высоты) и выравниваясь относительно текущего элемента, выравнивает элемент.
    Пример для Arial и Times. Поскольку такое решение – на подобии второго примера, наверное, можно сделать и на подобии первого: элемент маленький, псевдоэлемент большой, элемент выравнивается относительно псевдоэлемента. Технически, правда, придётся сделать наоборот – выравнивать псевдоэлемент относительно элемента, но с тем, чтоб двигался именно элемент. Возможны нюансы :-) Рассмотреть подобный случай оставлю на Ваше усмотрение…
    В итоге, я нашёл два способа: один когда регулируется маленький элемент относительно большого, и второй – когда два одинаковых (или близких по размерам?!) элемента, и они регулируются друг относительно друга, один упирается в верхний край строки, другой — в нижний. Какой из способов лучше? Даже не знаю, пожалуй, третий :-) Вариант с псевдоэлементом, несёт, пожалуй, наибольшую практическую ценность, поскольку не затрагивает блочные настройки. Но я делал псевдоэлемент по второму варианту, можно попробовать и сделать по первому… Думаю, что понимание, как работают оба способа, полезно в любом случае и рано или поздно может Вам пригодится :)

  10. Что-то я не могу найти горизонтальные характеристики шрифта Georgia, а именно letter-spacing (именно это значение, насколько я понимаю, используется для рассчитываня отступа между инлайн-блоками), перерыл весь FontForge, там есть какие-то туманные характеристики, вроде максимальное и минимальное растяжение пробела, а также пробел в конце предложения, впрочем, возможно в зависимости от пары символов расстояние между ними будет различаться… Ну а какое оно тогда между инлайн-блоками?
    opentype.js указанный в статье тоже не понятен, скармливаешь ему шрифт, он этим шрифтом пишет некий текст, а список параметров так и приводится в виде ссылок, нажимая на которые, попадаешь в некие пространные описания…

    1. Я полагаю, что расстояние между инлайн-блоками — это обычная ширина (advance width) символа пробела (код 32). А эксперимент показывает иное?

  11. Приветствую! Мне понравилась ваша статья. Благодарю.

    1. Но пробелы все равно остались. Здесь один пример. Элементы строки будут выровнены если для ячейки поставить «line-height: 0;». Возникает вопрос. Если строка «схопнута», то как она вообще отображается? Может быть следует пересмотреть формулировку «область содержимого всегда будет посередине виртуальной области.» Возможно это виртуальная область всегда посередине области содержимого? Иначе почему элементы отображаются полностью, а не только верхняя их половина?

    2. Так мне осталось не понятным чем же определена виртуальная область. Рисунок line-boxes.png определяет line-height исходя из максимальной высоты области содержимого одного из элементов строки. Откуда берутся верхний и нижний отступы в рисунке line-height.png?

    1. Прошу прощения за задержку комментария (ссылки на примеры ошибочно насторожили спам-фильтр, но недоразумение устранено). Наверное, проще ответить на вопросы в обратном порядке:

      2. Высота «виртуальной области» всегда задается значением line-height. Но при line-height: auto само это значение вычисляется по метрикам шрифта, как Ascend + Descend + Line Gap (см. пример в разделе про line-height). Поскольку область содержимого — это Ascend + Descend, то получается, что при дефолтном  line-height: auto «виртуальная область» выше области содержимого на значение метрики Line Gap. Если значение этой метрики равно нулю (как в шрифте Catamaran, на котором сделано большинство примеров к статье), то высоты этих областей равны.

      1. В более строгой формулировке, верхний край области содержимого всегда смещен относительно верхнего края «виртуальной области» на половину разности их высот. Если line-height больше высоты области содержимого, то эта разность положительна и содержимое смещается вниз, а если меньше ее, то разность отрицательна и содержимое смещается вверх. И на эти пол-разности торчит из контейнера строки наружу, как будто у него отрицательный margin-top.  С нижними краями всё аналогично, симметрично, поэтому конструкция всегда отцентрирована. Если таких строк несколько, то «выступающие за габариты» части символов будут перекрываться.

  12. Самое сложное в создании контейнера строки то, что мы не можем ни толком это увидеть, ни управлять этим из CSS. Даже задание фона для ::first-line не помогает представить высоту первого контейнера строки наглядно.

    Даже codepen не успел открыть, моментально убили идею :first-line )

  13. Сколько придирок к переводу, когда читаешь оригинал явно так не заморачиваешься, хороший перевод очень сложной статьи, спасибо большое!

  14. Здравствуйте, как задать отступ блока DIV от верха, при изменении ширины экрана монитора, смарта и пр…?

Оставить комментарий

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

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.