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

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

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

Страт (Strut)

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

В блоке всегда имеется "дефолтный" текстовый потомок, задающий шрифтовые параметры по умолчанию (в т.ч. line-height) для инлайнового содержимого этого блока. Если в блоке есть обычный текст, то в роли "дефолтного" текстового потомка выступает порождаемый этим текстом анонимный инлайновый бокс. Если же текста нет, то форматирование идет так, как если бы он был, как будто такой инлайновый бокс (с теми же свойствами) есть. Вот этот воображаемый инлайн и называется стратом – "Strut" (название навеяно редактором TeX).

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

<div class="withOutText">
	<span></span>
</div>
div {
	border: 1px solid #000;
	padding:0 10px;
	font-size: 20px;
	background: #F90;
	width: 150px;
}
span {
		display: inline-block;
		width: 70px;
		height: 70px;
		background: #900;
	}

И сразу результат:

Итак, есть блок, в котором находится элемент <span> (красный квадрат). Натурального текста в блоке нет, но вместо него там находится "воображаемый бокс", который и является нашим Strut'oм (на картинке я специально сделал его видимым). Т.е. текста в блоке нет, но он всё равно условно как бы есть. Так же, как и у обычного текста, у страта есть базовая линия, и его высота высчитывается метриками шрифта, о которых мы говорили в прошлых статьях.

Высота <span>'a (инлайн-блока) заставляет лайн-бокс растягиваться, но при этом высота Strut'a не меняется. Вместо того, чтобы растянуться, он поднимается или опускается в зависимости от базовой линии самого блока, т.к. его базовая линия привязана к общей базовой линии строки.

Vertical-align

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

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

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

baseline

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

middle

Вот здесь уже интереснее. Значение middle выравнивает середину бокса по середине строчных букв текста. Таким образом, "родительская" базовая линия оказывается на половину высоты строчных букв (так называемой "x-высоты", ей в CSS соответствует единица ex) ниже середины нашего бокса.

Сразу перейдём к примеру:

<p>xxx <span>100x100</span> xxx</p>
span {
	display: inline-block;
	width: 100px;
	height: 100px;
	background: #aa0;
	vertical-align: middle;
}
p {
	border: 1px solid #0a0;
}

И результат:

Нам уже известно, что высоту строки определяют самые высокие боксы в ней, поэтому наш инлайн-блок растянул строку ровно на 100px. У инлайн-блока стоит vertical-align в значении middle. Но, т.к. общая базовая линия идёт по нижнему краю букв "х", а положение середины элемента зафиксировано относительно положения базовой линии текста (на половину высоты строчных букв выше нее), то этой базовой линии приходится сместиться, подстраиваясь под самый высокий бокс.

Если говорить точнее, высота строки сейчас задается инлайн-блоком, но базовая линия абзаца привязана к отметке 50рх + 0.5ex от верха (или 50рх0.5ex от низа). Инлайн-блок ("высокая птица") немного подтянул базовую линию ("проволоку") как ему удобнее, но при этом остался к ней привязанным.

sub

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

Пример:

<p><span>xxx_</span><i>_100x100_</i><span>_xxx</span></p>
p {
	border: 1px solid #0a0;
padding: 0 10px;
}
span {
	background: #88f;
}
i {
	display: inline-block;
	background: #aa0;
	vertical-align: sub;
}

Результат:

Я поставил подчёркивания, чтобы вы могли видеть базовую линию. Она осталась на своём месте, а вот элемент <i>, у которого стоит vertical-align: sub — опустился вниз на отведённое место для нижних индексов. 

Что есть "Место нижних индексов"? Это уровень, отведенный для подстрочных индексов (как "двойка" в H2O), которые в HTML оформляются тегом <sub>. Точное значение их местоположения спецификация не определяет, так что браузеры и ОС имеют право на "немного самодеятельности" (а иногда и метрики шрифта могут на него повлиять).

super

Значение super аналогично sub, только на место для верхних индексов (как в E=mc2, в HTML<sup>).

text-top

Значение text-top выравнивает верхний край инлайн-бокса по верхнему краю области контента предка.

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

 <p><span>xxx_</span><i>_100x100_</i><span>_xxx</span><img src="img.jpg"></p>
span {
	background: #88f;
}
i {
	display: inline-block;
	background: #aa0;
	font-size: 16px;
        vertical-align: text-top;
}
p {
	border: 1px solid #0a0;
	font-size: 32px;
	padding: 0 10px;
}

И результат:

Я нарочно поместил в лайн-бокс картинку, чтобы вы могли увидеть ту самую контентную область  (которая не зависит от высоты строки), по верху которой и равняется элемент с text-top (оранжевый бокс в нашем случае). Это та самая контентная часть, которая  определяет высоту строки по дефолту. Браузер может, например, брать размер кегельной площадки или координаты максимальных верхних и нижних выносных элементов символов шрифта (см. предыдущие статьи).

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

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

text-bottom

Значение text-bottom аналогично text-top, только в отличие от последнего, выравнивает нижний край инлайн-бокса по нижнему краю области контента предка.

Важное примечание

Выше мы описали, как должно происходить форматирование строки согласно спецификации CSS 2.1. Нужно ли говорить, что в реальности всё не совсем так, и браузеры (даже с виду приличные) часто имеют своё "особое мнение". Но об этих выкрутасах отдельных браузеров мы расскажем уже в следующей части нашего цикла.

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

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″).

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

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

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

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

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

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

  1. Алексей

    Так почему в примере, сам текст-то внутри инлайн-блока ушёл вверх? Положение базовой линии основного текста понятно, а вот внутри блока — нет. Объясните, плиз :)

    1. SelenIT

      Речь, как я понял, про пример с middle и текст внутри зеленоватого квадрата? Дело в том, что любое значение vertical-align, кроме baseline, по сути разрывает связь базовой линии самого элемента и его окружения. И инлайн-блок выравнивается в строке как «просто прямоугольник» с известными габаритами, а внутри него содержимое форматируется как в обычном блоке — без какой-либо оглядки на его окружение.

      1. Алексей

        > любое значение vertical-align, кроме baseline, по сути разрывает связь базовой линии самого элемента и его окружения —

        а можно ли каким-либо способом эту связь разрывать и восстанавливать произвольно?

  2. Алексей

    Ну то есть, получается, что middle работает для окружения inline-block? Ведь если посмотреть, именно анонимные инлайн боксы (правильно назвал?) расположились на расстоянии 50рх – 0.5ex от низа. Видимо сделали это подстроившись под самый высокий элемент в строке, который и назначил базовую линию.
    Но, как я понял из Вашего ответа, внутри него это базовая линия не действует, поскольку vertical-align работает только для строчных элементов, а внутри этот элемент — блочный. По этой же причине его приходится назначать отдельным строчным элементам, а не блоку P.
    Ок, сделал строку внутри инлайн-блока и стал назначать свойства для неё. Вне зависимости от line-height, который отталкиваясь от верха блока, делает положение строки ниже, свойство vertical-align: middle делает строку чуть-чуть ниже по сравнению с baseline:

    http://jsfiddle.net/Launder/e17qxb28/

    Почему?

    1. SelenIT

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

      За второй вопрос спасибо, заставил задуматься.

      Насколько я сам понимаю, внутри блока действует базовая линия его strut-а (у которого line-height равна 60px, унаследованных от самого .block, поскольку он тоже span). При baseline вложенный элемент выравнивается по ней, а при middle пытается выровнять его геометрическую середину по середине строчных букв этого strut-а — т.е. на (0.5em — 0.5ex) ниже.

      1. Алексей

        Ну да, точно… :) Спасибо!
        ЗЫ: у меня в настройках отправки комментария стоит, по умолчанию «Получать новые комментарии по электронной почте», выбрано «Ответы на мои комментарии», но почему-то ответы на электропочту, к сожалению, не приходят. Возможно, какой-то баг, проверьте, пожалуйста. Если я чего напутал — звиняйте :)

      2. Алексей

        Пожалуй, всё же, напишу подробней, поскольку, то, как я первый раз я понял, оказалось поверхностным, и остались вопросы, над которыми пришлось задуматься, более тщательно вникая в Ваш ответ.
        Как я, в результате, понял:
        1. Внутри внутреннего span действует базовая линия его strut, который сам размером с font-size, при этом его базовая линия находится на расстоянии line-height от верхнего края блока, поскольку мы считаем верх блока некоей проволкой, на которой, якобы, расположились элементы высшей строки, соответственно, наша базовая линия располагается на величину line-height (-60px) ниже.
        Далее. Нам нужно чтоб середина нашего strut, который равен величине 0.5em оказалась на середине строчных букв, которая равна 0.5ex.
        Вообще для меня это было не совсем очевидно, мне почему-то казалось, что базовая линия должна подниматься на величину 0.5ex. При этом я упустил, что в середине должна быть не базовая линия, а именно середина шрифта, соответственно базовая линия должна располагаться на 0.5em ниже этой середины.
        Соответственно, базовая линия мысленно сдвигается на середину ex(0.5ex), а дальше уже опускается на величину 0.5em, с тем чтоб выравнивание было ровно по середине букв и располагалось по середине строчных букв, при базовой линии по умолчанию.
        Тогда расположение базовой линии вычисляется по той формуле, которую Вы привели.
        Ещё раз спасибо!

      3. Алексей

        Сделал чуть более расширенную иллюстрацию, по которой можно поэкспериментировать с этим свойством:

        http://jsfiddle.net/Launder/xLy0k599/1/

        ставим любому из txt line-height: 100px (равный размеру содержащего блока).
        текст чуть ниже середины — середины текстовых площадок чуть выше
        середины букв, а равные по размеру халф-лидинги, идут от середины текстовых площадок, середины же букв получаются чуть ниже.
        теперь эксперементируем со свойствами vertical-align. Напомню, по умолчанию vertical-align: base-line;
        Пусть line-height: 100px у txt, а vertical-align: middle у txt2. txt2 спустился чуть ниже, и своими серединами площадок «дышит» в середину xxx бокса txt. Поскольку середины площадок выше середины буквы x, то серединам пришлось спустится
        чуть ниже, чтоб стать вровень с xxx. Это объясняет сдвиг блока txt2.
        Пусть теперь vertical-align: middle у txt. txt2 поднялся, и серединами своих xxx сравнялся с серединой блока. Поскольку line-height самый большой у txt, то он расположился так, как ему удобно, а базовая линия поднялась чуть выше, таким образом, чтоб серединой своих xxx разместится по середине текстовых площадок txt. txt2 лежит на базовой линии, и замечательно это иллюстрирует.
        Также, если обоим боксам задать vertical-align: middle, то визуально результат будет такой, буд-то оба лежат на baseline и ничего не поменялось относительно базового варианта. Но это только видимость, из предыдущего примера видно, что изменилось положение базовой линии!
        И то, что txt теперь лежит рядышком с txt не меняет того, что любой другой текстовый элемент, который будет, по-умолчанию, лежать на базовой линии
        поднимется выше и будет лежать там, где в предыдущем примере лежал txt — серединой своих xxx будет находится по середине блока.

      4. Алексей

        http://jsfiddle.net/95jeu3u1/1/

        Мы назначили line-height содержащему блоку, величина равна размеру блока. лайн-хейт состоит из текстовых площадок + равные халф-лидинги сверху и снизу (когда размер превышает размер текстовых площадок), что означает что текст будет находится примерно по середине. По факту, текст находится чуть ниже середины (середина x ниже середины блока) — середины текстовых площадок чуть выше середины букв, из-за этого сами буквы располагаются чуть ниже середины площадок, и, соответственно, середины блока.
        Поскольку line-height свойство наследуемое, мы вложеным текстовым боксам присваиваем line-height: normal, чтоб отменить большой лайн-хейт предка. И тем не менее, когда мы внутри span ставим перенос строки, у нас она переносится на весь лайн-хейт содержащего блока.
        Почему?

  3. Алексей

    А почему у Вас strut в первом примере лежит так высоко?

    https://jsfiddle.net/Launder/nskhbdo6/

    Вроде как он должен вплотную прилегать к нижнему краю, за счёт лайн-бокса и инлайн-блока наш див и растягивается. Лайн-бокс, и, соответственно, strut должен совпадать с размерами с . А размер em видно по его фону.
    Правильно я понимаю, что инлайн-блок лежит на базовой линии?
    Интересно, что если бы инлайн-блоке был хоть какой-то текст, то всё бы изменилось, поскольку базовая линия блока бы съехала и стала в один ряд с текстом инлайн-блока. Почему — понятно из «высота строки, часть 4″.

    1. Алексей

      Да, маленькое примечание: в моём примере красная линия, торчащая из инлайн-блока, по-идее, не должна совпадать с базовой линией. Хотя, если б она с ней совпадала, это было бы, пожалуй, более наглядно. Но, в моём примере, красная линия, вероятно, ЛЕЖИТ НА БАЗОВОЙ ЛИНИИ, а значит выше её на один пиксель.
      Это верно, исходя из допущения, что инлайн-блок расположился на базовой линии.

    2. SelenIT

      Действительно, нижний край strut-а должен упираться в низ контейнера. Спасибо за внимательность!

  4. Алексей

    Оказывается очень удобно вписывать картинку в блок, для дальнейших манипуляций:

    https://jsfiddle.net/Launder/37eweLvf/5/

    У нас в блоке картинка, по-умолчанию, лежит на базовой линии. Наиболее простой и понятный способ убрать расстояние под базовой линей — это назначить блоку высоту равную размеру картинки, ну а вложенным блокам 100%. Но такое решение несколько топорно — возможно размер содержащего блока и стоит указать — но для других целей, и, в зависимости от целей, нужно искать и решения.
    А для данной задачи нужно во-первых осознать откуда берётся небольшой отступ снизу под картинкой у содержащего блока, во-вторых сместить картинку с базовой линии. Кроме значения middle, можно использовать, например, bottom, в общем можно поиграться с иллюстрацией.

  5. Алексей

    Похоже, strut не так безобиден, как может показаться.

    Вот в этом пример:

    https://jsfiddle.net/Launder/h0bs30q0/4/

    как можно увидеть, между двумя «литерными» площадками расстояние больше одного пикселя (по-моему три-четыре пикселя). И это при том, что символ переноса я специально «положил» в спан с маленьким размером шрифта!
    А если мы спану в нижней строчке присвоим размер шрифта равный размеру верхней строчки — 21 пиксель, то мы увидим что расстояние между строчками сократилось до одного пикселя, хотя мы и увеличли размер шрифта!
    По всей видимости, причиной, такого странного поведения является шрифт по-умолчанию, для блока. Насколько мне известно, в большинстве браузеров он составляет 16 пикселей.
    Я его поместил в родительский контейнер и задал в явном виде. И, по мере уменьшения размера шрифта уменьшился и размер межстрочного промежутка. На 14px он составил один пиксель, а для 12px стал нулевым. И далее, при сокращении font-size до нуля, вертикальное расстояние между двумя строчками осталось равным нулю.
    Если же мы при нулевом шрифте родителя зададим значение обоим спанам по 21px, то мы вновь увидим, между строчками промежуток в один пиксель(не смотря на нуль родительского элемента). Если увеличим размер, скажем до 50px у каждого спана, то межстрочный промежуток увеличится. И, если мы, теперь, обратно зададим родителю размер шрифта в 16 пикселей — ничего не изменится, «работать» будет 50px у потомков.
    В общем, похоже, strut это некий практически полноправный участник «строчный» жизни и, незримо присутствует в жизни своих потомков…
    Но как избежать его влияния, если оно чем-то мешает? Ведь даже если мы весь текст «положим» в один спан и поставим в нём символ переноса, если для родительского блока размер шрифта больше (а значит и strut), то промежуток между строчками будет по бОльшему strut.
    Насколько я понимаю, единственный вариант: сделать блок-обёртку, в котором strut будет нужного нам размера:

    https://jsfiddle.net/Launder/h0bs30q0/5/

    если же strut совсем не нужен, то его можно попросту обнулить и тогда, насколько я понимаю, промежуток между строками будет определяться кеглем шрифта и, возможно, отчасти, браузером. Скажем для 16px (шрифт Arial) между строчками будет полосочка в один пиксель, а для 12px — нулевая.
    Если же у нас шрифты относительные, то можно пользоваться rem — восстанавливать для потомков шрифт относительно корневого элемента.

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

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

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

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