ИКФ: Вертикальное выравнивание в строке, часть 2 (9-я публикация цикла “Тайны CSS2.1″)

top и bottom

Если с вышеупомянутыми значениями было всё достаточно прозрачно, то с top и bottom не всё так просто. У этих значений есть особая черта, которой нет ни у одного другого значения vertical-align. Инлайн-боксы с vertical-align и значениями top или bottom способны отвязывать себя от базовой линии.

Но всё же, давайте по порядку.

В спецификации сказано:

Следующие значения (top и bottom) выравнивают элемент по отношению к лайн-боксу. Поскольку у элемента могут быть потомки, выравненные относительно него самого (а у них — в свою очередь, тоже такие потомки), эти значения применяются к границам всей ветки DOM-дерева, к которой применяется выравнивание (aligned subtree, букв. "выравниваемое поддерево"). Это выравниваемое поддерево инлайнового элемента содержит сам элемент и выравниваемые поддеревья всех его потомков с действующим значением  'vertical-align' не 'top' и не 'bottom'. Верх выравниваемого поддерева — самый верхний из верхних краев боксов в поддереве, низ — аналогично (т.е. самый нижний из нижних краев)

Проще говоря, при top и bottom положение таких элементов, которые могут иметь свой лайн-бокс (например, инлайн-блок), привязано только к границам внешнего лайн-бокса и они игнорируют общую базовую линию. Внутри такого элемента действуют те же законы инлайн-форматирования, и абсолютно не важно, какое в нём содержимое, нам важна только его итоговая высота.

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

<p>
    Внешний текст:
    <span class="one">
        strut 1
        <i>Раз</i>
        <i>Два</i>
    </span>

    <span class="two">
        strut 2
        <i>Раз</i>
        <i>Два</i>
        <i>Три</i>
    </span>

</p>
 p { border: 1px solid blue; }
span , i {
	display: inline-block;
	border: 1px solid red;
	vertical-align: bottom;
}

.one > i {
    height: 60px;
    border: 1px solid blue;
    vertical-align: top;
}

.one > i + i {
    vertical-align: bottom;
	height: 100px;
}

.two > i {
    height: 100px;
    border: 1px solid green;
    vertical-align: baseline;
}

.two > i + i {
    vertical-align: text-top;
}

.two > i + i + i {
    vertical-align: middle;
}
​

И результат:

Я немного усложнил пример, но это было необходимо для того, чтобы ничего не упустить. Поэтому будем разбираться очень подробно.

Два изображения. На первом показано поведение всех браузеров, кроме Opera, а на втором уже самой Opera (В Chrome и Safari есть небольшое отличие из-за интересного бага, но оно не относится к нашей теме, поэтому об этом позже). Я специально сделал скриншот из Opera, чтобы можно было увидеть отличие в отображении внешнего текста в блоках, в которых содержатся элементы с vertical-align: top или bottom. В большинстве браузеров внешний текст прижат к низу своих контейнеров, но в Opera к верху.

А теперь по порядку.

У нас есть блочный контейнер (<p>). Внутри него я поместил "Внешний текст" и два инлайн-блока (<span>), каждый из которых выровнен по низу лайн-бокса при помощи vertical-align: bottom. Эти элементы растягивают контейнер по высоте самого высокого из них, в данном случае — по высоте второго элемента. Аналогичное поведение происходит и в первом инлайн-блоке, с единственным отличием, что элементы внутри него прижаты к верху и низу соответственно.

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

Вот что по этому поводу примерно написано в спецификации:

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

Выходит, что Opera "прибивает" к "потолку", остальные к "полу". И никто не нарушает спецификацию. Ведь если самые высокие боксы в строке сорваны с базовой линии (у них vertical-align top или bottom), то сама эта базовая линия ни к чему не привязана и может находиться где угодно, главное, чтобы из лайн-бокса ничего не торчало наружу. 

Кстати, обратите внимание на основной текст в инлайн-блоках, который я специально обозначил, как "strut 1" и "strut 2". Этим самым я хотел показать, что если бы в элементах не было бы текста, то в нём обязательно находился бы воображаемый бокс (strut), который выравнивался бы по основной базовой линии в боксах.

Ну и последний инлайн-блок в параграфе содержит элементы, vertical-align которых не имеет значений top или bottom и, соответственно, никто из них не сорван с базовой линии и выравнивается уже относительно неё. Этот случай не стоит отдельного разбирания, т.к. всё из чего он состоит, мы уже обсуждали в этой статье. Попробуйте пока сами проанализировать ситуацию и разобраться, что происходит в блоке. А позже мы обязательно разберём сложные и, возможно, не совсем очевидные случаи.

Занятный баг в Webkit

Ну и напоследок, в этой части рассказа, я бы хотел рассмотреть один занятный баг в браузераx на базе движка Webkit (Chrome и Safari). Он не относится напрямую к vertical-align top или bottom, а, скорее, к наследованию vertical-align.

Чтобы было понятно, о чём идёт речь, сразу начну с примера, код которого один в один совпадает с кодом вышеразобранной ситуации, но немного отличается своим результатом. Выше я приводил два скриншота, но нарочно не стал показывать скриншот из Chrome или Safari, потому что это не относилось к теме. А вот теперь другое дело, смотрим:

Здесь стоит обратить внимание на последний блок, текст ("strut") которого выровнен по нижнему краю. Это поведение является неверным по той простой причине, что vertical-align не наследуемое свойство, но вот Webkit с этим не согласен, и вместо того, чтобы выравнивать сам инлайн-блок (с красной рамкой) в его родительском лайн-боксе, он вторгается и во внутренний лайн-бокс потомка. Интересно, что баг действует только на анонимный инлайн-бокс (текст "strut 2 "), остальные потомки этого инлайн-блока — вложенные инлайн-блоки с зеленой рамкой — выравниваются по вертикали как обычно.

Вот такие вот весёлые баги встречаются порой в разных браузерах. Так что учитывайте это.

Абсолютные единицы

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

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

<p>
	<span>baseline</span>
	<em>16px</em>
	<i>1em</i>
</p>
span { vertical-align: baseline; background: #cc8; }
em{ vertical-align: 16px; background: #F93; }
i{ vertical-align: 1em; background: #FC0; }

p {
	border: 1px solid #0a0;
	font-size: 32px;
}

И результат:

В блоке находятся три элемента. Первый из них (<span>) стоит на общей базовой линии, как по умолчанию. Второй элемент (<em>) визуально сместился* ровно на 16px (именно такое значение у него и выставлено). Ну, а у третьего элемента (<i>) выставлено значение 1em, и, так как единицы измерения em высчитываются относительно размера родительского шрифта (а последний у нас равен 32px), то 1em можно пересчитать в 32px. Выходит, что <i> *сместился на 32px.

Точно так же происходит и с отрицательными абсолютными единицами, только в этом случае элементы уже опускаются вниз на выставленное значение. Рассматривать поведение отрицательных значений не имеет смысла из-за его явной очевидности.

*Да, и не забываем, что боксы раскладываются сверху вниз, поэтому поднятие элементов при положительных значениях происходит только визуально, и что на самом деле опускается именно общая базовая линия

Проценты

Проценты поднимают (положительное значение) или опускают (отрицательное) бокс на расстояние в процентах от значения line-height. А если нет явно заданного значения, то последнее берётся от наших старых знакомых — метрик шрифта. 0% — то же самое, что baseline.

Положительные значения

Надо заметить, что в браузерах есть отличия в поведении боксов, как с положительными, так и в отрицательными значениями, а в Chrome и в Safari даже существует баг, который мы оставим на закуску.

Первый пример (общая высота шрифта):

<p>
    <span>xxxL</span>
    <i>xx 100% xx</i>
    <em>xx 33% xx</em>
    <span>Jxxx</span>
</p>​
span {
    background: #88f;
}
i {
    background: #aa0;
    vertical-align: 100%;

}
em {
    background: #aa0;
    vertical-align: 33%;
}
p {
    border: 1px solid #0a0;
    font-size: 32px;

}​

Результат таков:

В данном случае во всех браузерах будет идентичная картинка. Здесь высота самих инлайн-боксов отсчитывается от общей высоты шрифта и составляет порядка 38px. Второму элементу (<i>xx 100% xx</i>) мы выставили положительное значение в 100%, что автоматически подняло его наверх ровно на свою высоту (38px). А вот третий инлайн-бокс (<em>xx 33% xx</em>) мы подняли на 33%, что составило примерно 12px.

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

Второй пример (своя высота шрифта):

Если результат с общей высотой шрифта идентичен во всех браузерах, то с конкретно заданным шрифтом элементу всё обстоит немного иначе.

Смотрим пример:

<p>
    <span>xxxL</span>
    <i>xx 100% xx</i>
    <em>xx 33% xx</em>
    <span>Jxxx</span>
</p>​
span {
    background: #88f;
}
i {
    background: #aa0;
    vertical-align: 100%;
    font-size: 50px;
}
em {
    background: #aa0;
    vertical-align: 33%;
}
p {
    border: 1px solid #0a0;
    font-size: 32px;

}​

И результат:

Единственное, что я сделал с кодом — это установил у второго элемента (<i>xx 100% xx</i>) размер шрифта (50px). Этого вполне было достаточно, чтобы заставить браузер Opera вести себя немного иначе, в отличии от своих "собратьев". Вместо того, чтобы сместиться на итоговую высоту элемента <i>, нижние элементы изменили свою вертикальную координату лишь на половину того, что от них требовалось. Скорее всего, это поведение можно отнести к багу Opera, потому что, во-первых, Opera — это единственный браузер с таким результатом, а во-вторых, в таком поведении не видно логики.

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

Отрицательные значения

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

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

<p>
    <span>xxxL</span>
    <i>xx -100% xx</i>
    <em>xx -33% xx</em>
    <span>Jxxx</span>
</p>​
span {
    background: #88f;
}
i {
    background: #aa0;
    vertical-align: -100%;

}
em {
    background: #aa0;
    vertical-align: -33%;
}
p {
    border: 1px solid #0a0;
    font-size: 32px;

}​

И результат:

За основу я взял первый случай с общей высотой шрифта, и заменил положительные процентные значения vertical-align на отрицательные. Ситуация зеркально изменилась, и теперь второй и третий боксы уже сместились вниз, а не заставили смещаться окружающие элементы, как в случае с положительными значениями.

Баг Webkit

В целом процентные значения с vertical-align в браузерах на базе движка Webkit (Chrome и Safari) работают нормально, но это до тех пор, пока дело не касается замещаемых элементов, таких как <img>, <input> и т.д. Применение к ним vertical-align в процентах  делает поведение этих элементов довольно таки занятным и отличным от остальных браузеров.

Сразу приведу пример:

<p>
    <span>xxxL</span>
    <i>xx 100% xx</i>
    <em>xx 33% xx</em>
    <span>Jxxx</span>
    <img  src="skyscraper.jpg">
</p>​
span {
    background: #88f;
}
i {
    background: #aa0;
    vertical-align: 100%;

}
em {
    background: #aa0;
    vertical-align: 33%;

}
p {
    border: 1px solid #0a0;
    font-size: 32px;
}

img { width: 100px; height: 100px;vertical-align: 100%; }

Результат:

В качестве испытуемого я подобрал картинку с габаритами 100х100px (но не забываем, что это касается любых замещаемых элементов). Назначив ей vertical-align: 100% мы заставили остальные элементы сместиться вниз на определённое расстояние. Но на какое именно? Вопрос именно в этом, потому что в большинстве браузеров элементы сместились на высоту метрик основного шрифта, а вот в Chrome и Safari на высоту самой картинки.

Кто прав, а кто не прав? Давайте разбираться. Как мы уже поняли, проценты в vertical-align высчитываются от значения line-height либо от метрик самого шрифта, но никак не от высоты лайн-бокса. Именно это мы и видим в большинстве браузеров. WebKit же включил в 100% высоту самой картинки. Пожалуй, в этом есть определенная логика (ведь именно от высоты картинки зависит фактическая высота лайн-бокса), но, к сожалению, спецификации эта логика не соответствует.

Добавление от 08.10.2014: к настоящему времени этот баг исправлен, в Chrome 38 (Windows 7) и iOS 7.1 Safari он уже не наблюдается.

Другие публикации цикла

1. Введение в инлайновый контекст форматирования (ИКФ): основные понятия (1-я публикация цикла “Тайны CSS2.1″).

2Введение в инлайновый контекст форматирования (ИКФ): основные понятия (2-я публикация цикла “Тайны CSS2.1″).

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

4ИКФ: высота строки, часть 2 (4-я публикация цикла “Тайны CSS2.1″).

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

6. ИКФ: высота строки, часть 4 (6-я публикация цикла “Тайны CSS2.1″).

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

8ИКФ: Вертикальное выравнивание в строке, часть 1 (8-я публикация цикла “Тайны CSS2.1″).

10. ИКФ: Вертикальное выравнивание в строке, часть 3 (10-я публикация цикла “Тайны CSS2.1″).

11ИКФ: Горизонтальное выравнивание, часть 1 (11-я публикация цикла “Тайны CSS2.1″).

12ИКФ: Горизонтальное выравнивание, часть 2 (12-я публикация цикла “Тайны CSS2.1″).

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

11 Комментарии

  1. usf

     базовая линия элемента <i> по итогу оказалась 100% процентов от своей высоты выше базовой линии окружающего нормального текста
    чот замудрили как-то) попроще ведь можно.

  2. Алексей

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

  3. Алексей

    Интересно, что vertical-align в значении 1em и 100% различаются. Дело, видимо, в том, что в одном случае font-size используется тупо как значение для сдвига, а во-втором случае сдвиг происходит от реального размера шрифта, который, как я понимаю, отличается от своего численного размера, а вот насколько, зависит от конкретного шрифта + браузер ещё добавляет тоненькую линию, между двумя лайн-боксами.

    https://jsfiddle.net/Launder/n817tfLv/1/ — иллюстрация.

    И вот ещё — разобрал пример, который Вы в тексте предложили для самостоятельного разбора:
    https://jsfiddle.net/Launder/orssauk6/

    я его прокомментировал подробно что зачем и от чего, если гляните и скажите, всё ли там правильно — мне будет очень приятно :-)

    1. Алексей

      https://jsfiddle.net/Launder/orssauk6/1/ — немного подкорректировал свой пример с разбором Вашего примера.

      1. SelenIT

        Практически всё верно, только в случае text-top блок оказался ниже не из-за шрифтовых метрик, а просто из-за однопиксельного border-а: https://jsfiddle.net/orssauk6/2/

        1. Алексей

          Спасибо Вам большое за пояснение!

  4. Алексей

    Я, конечно, извиняюсь, а есть ли какие-то особенности вертикального выравнивания в таблицах, на которые стоило бы обратить внимание?

    1. SelenIT

      Есть такое. Главное — для ячеек таблиц (и любых элементов с display:table-cell) иначе работают top/bottom/middle: они выравнивают не относительно контейнера строки (который «line box»), а относительно всей ячейки (на этом основан один из популярных способов вертикального центрирования). А все прочие значения, кроме этих трех и baseline, по спецификации должны действовать как baseline.

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

      1. Алексей

        Не совсем понятно есть ли отличие между baseline и top? А также middle, который, как я понимаю, в таблице по умолчанию, центрирует текст по середине x? или по середине шрифтовой площадки (которая, вроде, то ли очень близка, то ли равна line-height и центр этой площадки, обычно выше буквы середины x) когда строчка одна. а когда не одна? чётное количество строк середина — между двумя центральными строчками, не чётное — среднюю строчку — как одну строчку?

        1. SelenIT

          Разница между baseline и top будет заметна, если появится что-то, что эту базовую линию (виноват, упустил важный момент: эта базовая линия общая для всей строки таблицы!) сдвинет. А middle, получается, да, выравнивает всё содержимое ячейки по габаритам, без оглядки на середину каких-либо букв, и если будет четное кол-во строк одинаковой высоты, то середина будет между двумя центральными.

          1. Алексей

            Понял, спасибо большое!

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

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

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

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