ИКФ: высота строки, часть 5 (7-я публикация цикла «Тайны CSS2.1»)

ИКФ: высота строки, часть 5 (7-я публикация цикла «Тайны CSS2.1»)

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

1. Overflow инлайн-блоков не влияет на положение их базовой линии (ссылка на баг).

2. Инлайн-таблицы выравниваются не по базовой линии первой строки, а по нижней границе всей таблицы. Этот баг уже исправлен и ближайшие релизы вот-вот начнут вести себя по спецификации.

line-height для замещаемых элементов

Поведение замещаемых элементов отчасти напоминает поведение инлайн-блоков по отношению к высоте строки, но в остальном они всё же отличаются. Но, давайте по порядку…

Что есть "Замещаемый элемент"

Для начала давайте выясним, о каких таких "Замещаемых элементах" идёт речь.

Замещаемый элемент — это элемент, который до загрузки документа имеет лишь пустой контейнер, а в дальнейшем подгружает своё содержимое из внешних источников. Т.е. его содержимое замещается чем-то, что не находится непосредственно в самом документе. Самый просто пример, это элемент <img>, который замещается файлом изображения. Так же существует ещё ряд замещаемых элементов, таких как: <video>, <object>, <input> (много разных типов) и т.д.

Высота замещаемых элементов

Если содержимое замещаемого элемента неизвестно заранее, то как же тогда узнать его высоту? В этом плане у замещаемых элементов есть несколько критериев, по которым можно определить их точную высоту. Предлагаю ознакомиться со всеми ними.

  • Как и у инлайн-блоков, высота замещаемых элементов так же может зависеть от вертикальных полей (margin) и прибавлять их значения к общей высоте элемента. Но, если нижние и верхние поля (margin) у замещаемых элементов имеют значение "auto", то итоговое значение их полей будет равно нулю.
  • Высоту замещаемым элементов можно задать непосредственно прямо в стилях либо в атрибуте, прописав в свойстве "height" нужное значение. Это хорошо в том случае, если мы хотим, чтобы высота замещаемого элемента была принципиального размера. В противном случае, если у замещаемого элемента высота и ширина имеют значение "auto", то его высота рассчитывается по контенту, т.е. попросту по загруженному содержимому. Например, высота картинки может зависеть от ширины и высоты внешнего файла, который в неё загрузится.
  • В случае, если высота замещаемого элемента "auto", и у элемента есть внутренние пропорции, то итоговая высота элемента будет равна: (итоговая ширина) / (внутренние пропорции (Ш:В)). Например, почти у всех фотографий с "зеркалок" пропорции 3:2, а с компактных фотоаппаратов 4:3. В случае 4:3 при высоте "auto" и чётко заданной ширине, допустим 100px, высота получится 100/(4/3) = (100 * 3)/4 = 75px. Надо понимать, что не все замещаемые элементы имеют внутренние пропорции, это в основном касалось таких элементов, как: <img>, <object> (если в нём есть картинка или что-то типа того), <input type="image"> и т.д. Все остальные элементы скорее попадают под следующий критерий.
  • Если вычисленное значение 'height' равно 'auto' и у элемента есть внутренняя высота, то эта внутренняя высота становится итоговым значением 'height'. Т.е. у многих элементов есть высота по умолчанию. Она зависит от предпочтений браузеров и в каждом из них может быть разной. Например, у того же <input type="file"> нет никаких внутренних пропорций, но зато есть своя, собственная высота.
  • И последний случай — это когда вычисленное значение 'height' замещаемого элемента равно 'auto', но ни одно из условий выше не выполняется. В таком случае итоговое значение 'height' должно устанавливаться как высота максимально возможного прямоугольника с пропорцией 2:1, который не выше 150px и не шире чем ширина устройства. Т.е. если у нас , к примеру, есть картинка без "родных" пропорций, то она считается как имеющая пропорции 2:1 и пытается втиснуться в ширину экрана и в 150px по высоте. Например, на большом экране размер у такой картинки будет 300х150, на маленьком её ширина будет равна ширине экрана, а высота — половине этой ширины.
Базовая линия замещаемых элементов

Относительно базовой линии окружающего текста замещаемые элементы ведут себя по-разному. Некоторые становятся на нее своим нижним краем (как пустой инлайн-блок), некоторые — нижним краем последней строки своего текста. В общем, давайте смотреть на примерах.

Начнём с тех элементов, которые встают на базовую линию своим нижним краем или полем (margin), если таковое имеется. Такими элементами являются, например, <img>, <object>, <video> и т.д.

Рассмотрим маленький пример.

Для примера я выбрал элемент <img>. Как видно из скриншота, его базовая линия совпадает с его нижним краем. В случае с нижним полем (например, margin-bottom: 20px) отсчёт базовой был бы уже от низа самого поля и выглядело бы это примерно так:

А вот с другими замещаемыми элементами, такими как: <select>, <input> (разными типами) ситуация немного поинтереснее. Она даже чем-то напоминает ситуацию с инлайн-блоками. Из следующего примера вы поймёте, почему я так говорю.

Обратите внимание, где находится базовая линия элементов, в которых есть текст. Правильно! Именно там, где заканчивается последняя строка в тексте. И лучше всего это можно заметить по элементу <button>, содержимое которого состоит из двух строк. Тоже самое у нас происходило с инлайн-блоками и отчасти с инлайн-таблицами, только в последних за основу для базовой линии брались ячейки первого ряда.

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

Система координат для замещаемых элементов

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

Здесь, в общем-то, ничего нового. Намного интереснее было бы поговорить о влиянии самого line-height на замещаемые элементы, потому что в этой области есть много интересных нюансов.

line-height не оказывает влияния на элементы <img>, <video> или <object>, но зато с такими элементами, как <select>, <button> или <input> (разными типами), происходят удивительные вещи.

Рассмотрим ситуацию на конкретном примере.

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

Почти во всех браузерах ситуация абсолютно разная. Единственные, кто солидарен между собой — это Firefox и Opera, именно их поведение я считаю правильным. Chrome и Safari позволили себе лишнего, заставив line-height влиять на <input type="text"> и на <input type="button">. А браузеры Internet Explorer вдобавок вообще сотворили нечто ужасное с <input type="text">. В итоге, предсказуемо ведёт себя только лишь элемент <button> (поведение которого напоминает поведение обычного инлайн-блока), а остальные — как повезёт.

Почему я считаю, что поведение Firefox и Opera самое правильное? Дело в том, что текст, который мы привыкли видеть в элементах <input>, на самом деле не является контентом. Атрибут value, в котором содержится этот текст, не отвечает ни за какой контент, а вместо этого является лишь свойством, которое, строго говоря, вообще не обязано отображаться. Вспомните тот же чекбокс (type="checkbox"), разве вы когда-нибудь видели, чтобы его "value" где-нибудь отображалось? Визуально, мы конечно же, можем видеть текст в самих <input>, но на самом деле это абсолютно пустые элементы.

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

Можно лишь предположить, что, например, Internet Explorer, скорее всего отображает текст в <input> по аналогии с отображением настоящего контента, но и другие браузеры делают что-то подобное.

Единицы измерения в line-height

Значения line-height делятся на следующие четыре группы: <число> (просто безразмерное число) , 'normal', <длина> (в любых CSS-единицах длиныem, px, cm и т.п.) и проценты.

Число

Число (или безразмерное число) в качестве значения line-height умножается на фактическое значение font-size, и тем самым получается нужная нам длина. Вот маленький пример:

Результат будет означать то же самое, что и font-size: 20px; line-height: 40px. Т.е. размер шрифта увеличился ровно вдвое. Соответственно единица в line-height: 1 — будет идентична установленному размеру шрифта.

Так же числа могут быть дробными и положительными. Отрицательные значения не допустимы.

normal

Если вы не установите никакого значения в line-height, то по умолчанию оно установится в значение "normal". Поэтому "normal" считается самым простым, но, в то же время, коварным значением.

В чём же его коварность? Дело в том, что это значение зависит от самого шрифта и от ОС/браузера. Поэтому может получиться, что в одном браузере это значение превратится в 1.1 (от размера шрифта), а в другом 1.2 или 1.3 с хвостиком. Следовательно, результат может быть совершенно непредсказуемым.

Какой же выход? Выход прост — явно задавать значения line-height, удобные для чтения. Например, 1.3 или 1.4.

Длина

Длина указывается в любых единицах измерения, а затем пересчитывается в пиксели. Например, 1in = 2.54cm = 25.4mm = 72pt = 96px. Разница с нормальной высотой делится пополам в виде half-leading'ов. Это как раз тот случай, который мы уже разобрали в прошлых статьях.

Единицы em для line-height писать бессмысленно, потому что line-height: 1.5em и line-height: 1.5 — одно и то же. Т.к. и там, и там 1.5 — множитель при font-size.

Проценты

Проценты — фактически тот же множитель к font-size, но умноженный на 100% . Т.е. 1.5em, просто 1.5 и 150% для line-height — одно и то же. А в остальном проценты точно так же пересчитывается в пиксели, как и обычная длина.

Родительский инлайновый бокс с дочерними инлайнами и разным line-height Вложенный инлайн с line-height больше родительского

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

Рассмотрим простой пример:

Перед нами абзац с текстом и инлайн-элементом <span> (выделен красным), внутри которого содержится текст, элемент <br> (для разрыва строки) и инлайн-элемент <i>. Сам элемент <span> вынужден разорваться на две части, последняя из которых включает в себя только лишь элемент <i>.

line-height span'a составляет 20px, а line-height его дочернего инлайна <i>50px, но при этом мы можем наблюдать следующую картину. Во второй части span'a, которая перенеслась на следующую строку, инлайн-элемент <i> своими half-leading'ами выпирает из своего родительского элемента (для наглядности я выставил вертикальные padding'и), но при этом высота самого родительского элемента не изменилась.

Суть в том, что инлайн сам по себе вообще никогда не растягивается по вертикали, потому что не умеет этого делать. У него нет ничего, что могло бы растягиваться от содержимого. Всё, что есть у инлайна — это площадки и (возможно) half-leading'и. Ни то ни другое — не контейнер, который может растягиваться содержимым.

В нашем случае получается следующее. Всё инлайновое содержимое span'a сидит на одной базовой проволоке, в смысле базовой линии. Но слева у нас сидят большие площадки с такими же half-leading'ми, а справа — маленькие с большими half-leading'ми.

Если наш span перенесется перед или после слова "ТЕКСТ" и займет два лайн-бокса, то высота первого будет 20px, а второго — 50 — по высоте наибольших инлайновых элементов. И то, что <i> является потомком того же инлайна, на высоту первого лайн-бокса не повлияет. А вот если хоть одна буква из слов "с маленьким вложением" останется на первой строке, то высота первого лайн-бокса окажется 50px, потому что самой высокой там будет уже именно эта площадка со своими half-leading'ми.

Вывод таков. Контейнером для инлайнов выступает лайн-бокс, и глубина вложенности инлайнов не имеет никакого значения. Вложенные инлайны не влияют на высоту внешних инлайнов, и каждый лайн-бокс работает только со своими площадками, и неважно, кто их родитель и где он начинается. У инлайнов строго говоря вообще нет высоты как таковой, высота есть только у лайн-боксов, а у инлайнов — только площадки.

Вложенный инлайн с line-height меньше родительского

Но в ситуации, когда у вложенного инлайна line-height меньше, чем у родительского, и при этом он переносится на новую строку, то высота этой строки определяется line-height'ом родительского элемента, а не вложенного.

Снова рассмотрим на примере:

Саму структуру описывать не имеет смысла. Она ничем не отличается от предыдущего примера. Единственное, что поменялось — это некоторые стили, а точнее line-hight span'a и вложенного в него инлайна <i>. Их line-height поменялся местами и теперь line-height span'a стал равен 50px, а вот у <i> — 20.

Здесь стоит обратить внимание на поведение вложенного инлайна <i>, который из-за маленького line-height'a болтается внутри своего родителя. несмотря на то, что <i>, по сути, единственный в span'e, кто перенёсся вместе с ним на следующую строку, высота последней строки не изменилась, а осталась родительской.

Спецификация объясняет это поведение так:

Высота инлайн-бокса включает в себя все глифы и их half-leading'и сверху и снизу, и тем самым является в точности line-hight'oм. Боксы дочерних элементов не влияют на эту высоту.

Другими словами, если высота потомка выше, она выпирает и влияет на высоту лайн-бокса, но не на высоту инлайна, а если она меньше, то она просто болтается внутри и не влияет вообще ни на что.

Что ещё следует знать?

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

📎📎📎📎📎📎📎📎📎📎