Загадка дыры
Будни верстальщика
Итак, вам поставили задание: сверстать баннер для свежей акции. Вы создали блок, установили границы, вставили в него ссылку с элементом img, но наблюдаете не самый ожидаемый результат: внизу изображения появился отступ непонятного происхождения, а в инспекторе вообще выглядит, будто кто-то дорисовал прозрачную полосу снизу. И как-то это всё не кажется нормальным.

Плохое решение
В приступе паники вы можете вспомнить заветы дедов и воспользоваться свойством float. А почему бы и нет, ведь везде пишут, что float специально придумали для врезки изображений в текст. Всё выглядит так, будто его логично здесь применить.

Задача решается так просто? К сожалению, нет. На скриншоте я намеренно схитрил, весь ужас происходящего раскрывается в коде примера.
При установке элементу свойства float равного left или right (отмена свойства – значение none) он вынимается из потока документа, перестаёт воздействовать на блочные элементы, а его размеры устанавливаются по содержимому. Слова же (строчные элементы) располагаются так, чтобы обтекать его.
Так вот, поскольку float-элемент вынимается из потока, высота блочного элемента схлопнется до размеров потокового контента, т. е. текста. В нашем случае текста нет вообще и это приведёт к вырождению блока aside, и вместо опоясывающей границы мы увидим толстую линию наверху.
Чтобы этого не происходило, элементу, содержащему img, нужно или установить display: inline-block, или прибегнуть к одному из множества clearfix-хаков (см. код примера). Но это выходит за рамки статьи, подробнее о float можно почитать на MDN или CSS-tricks. Свойство float пало жертвой собственной мощности.
Полное решение
Паниковать не стоит, а вот что нужно сделать – это заглянуть в спецификацию или хотя бы в средства разработчика, встроенные в браузер. Спецификация скажет нам, что img – это потоковый (flow), фразовый (phrase) и встроенный (embedded) элемент (остальные пункты нам пока неинтересны):
- потоковый указывает, что он влияет на раскладку (поток) документа, будучи вставленным в него (практически все HTML-элементы – потоковые);
- фразовый – что элемент является текстом, т. е. может быть помещён в строку текста и рассматриваться как отдельное слово;
- встроенный – что данные загружаются извне (в простейшем варианте) и сами по себе являются отдельным ресурсом.
Фразовый контент является частью потокового. Большинство элементов этого типа могут содержать в качестве своего содержимого только элементы из своей собственной категории. В определении и кроется корень наших проблем.
Раньше, до появления HTML5, не было понятия фразового, но было понятие строчного (inline) элемента. Возможно, с этой точки зрения начать рассмотрение проще, поскольку слово inline вам может быть знакомо: это одно из значений CSS-свойства display. Именно его можно увидеть у тега img, если посмотреть в средства разработчика. Означает оно, что элемент будет рассматриваться как слово:

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

Некоторую закономерность вполне можно проследить. Изображения явно выравниваются по одной линии с буквами. Пришло время немного погрузиться в строчный (inline, а чаще просто инлайновый) контекст форматирования.
Как мы знаем, текст состоит из слов, которые складываются в строки. Между строками всегда есть некоторое расстояние, называемое интерлиньяж или, буквально, межстрочный интервал. Этот самый интервал описывается свойством line-height и обычно рассчитывается браузером автоматически исходя из свойств и размера шрифта.
Эти свойства шрифта включают в себя выносные элементы, высоту и акценты букв (ф, ё, б, р, у, й, l, p, d, b, g, j, ä). Сами же символы выстраиваются по базовой линии. Воспользуемся определением из Википедии:
Базовая линия (или линия шрифта) – воображаемая прямая линия, проходящая по нижнему краю прямых знаков без учёта свисаний и нижних выносных элементов. В строке символы текста стоят на базовой линии, а нижние выносные элементы текста «свисают» с неё.

Итак, разгадка проблемы сияет перед нами. Ещё раз посмотрим на пример выше и увидим, что браузер ставит нижний край изображения на базовую линию, но высота строки же ей не ограничивается! Таким образом, под изображением в строке остаётся ещё немного пустого пространства которое в условиях, когда img один, превращается в пресловутый отступ.
Для управления позиционированием текста в строке существует свойство vertical-align. Его не стоит путать с аналогичным для ячеек таблиц, у них разные цели. Подробнее о значениях можно прочесть на MDN.
В документации мы можем найти несколько интересных значений: middle заставит элемент выровняться по середине высоты строчной буквы x, а top и bottom выровняют элемент относительно, соответственно, верхнего и нижнего края родителя (строки).
Бинго! Мы можем использовать различные значения vertical-align чтобы разобраться с отступом и при этом не забыть о тексте, управляя его положением относительно изображения.

Казалось бы, ну откуда в ссылке, содержащей только изображение и ничего больше, возьмётся пресловутая высота строки? Ведь текста никакого нет, зачем браузеру так нас подставлять?
Дело тут в том, что даже если текста нет, блок наследует свойства шрифта от родителя, продвигаясь вверх по дереву элементов. В итоге в, казалось бы, чистом коде мы наследуем свойства шрифта от корня (:root, тег html). А они, в свою очередь, определяются браузером. Это навевает некоторые новые мысли и приводит нас к следующему решению.
Хакнутое решение
Нам известны две вещи:
- браузер рассчитывает
line-heightисходя из различных свойств шрифта, включая размер (font-size). - свойство
line-heightможет принимать числовые значения, включая0.
Но это значит, что мы можем убрать базовую линию вообще, установив нулевой размер шрифта (с сохранением автоматического (normal) межстрочного интервала, это важно), или просто установив нулевой line-height. Конечно, в первом случае мы вообще потеряем текст, а во втором текст выровняется по вертикали крайне неожиданным образом, но кого это сейчас волнует?

Простое решение
Но мы же можем вообще избавиться от фразовых свойств. Превратим элемент в блок используя свойство display: block. Вот и всё простое решение! Посмотрим, что получилось.

Отступ исчез! Вот только оно не всегда нам подходит. Что если помимо изображения мы всё же в ссылку захотим добавить текст, причём, не под картинкой, а рядом? Или же вы собрались верстать руководство пользователя и нужно в строку вставить символы или сымитировать кнопки на каком-либо приборе. Блочное отображение тогда совсем не то, что нужно, а с вертикальным выравниванием – всё-таки достаточно сложное для понимания и управления. Мы же в 2020 году, неужели нет ничего одновременно гибкого и простого?
Прекрасное решение
Такое решение есть. И это знаменитый flexbox. Да, флекс хорош не только для формирования структуры страницы, списков или крупных блоков, но и для гораздо более прозаичных и мелких задач, как наша.
Если вы ещё не знакомы с ним, крайне рекомендую всё те же MDN и CSS-Tricks. Нам же сейчас интересно следующее: flexbox определяет собой новую модель раскладки, буквально гибкую: flexible box model. Не вдаваясь в подробности это означает, что мы получаем возможности блочной и строчной модели без их минусов в одной упаковке: можем выстраивать элементы в одной строке или колонке и задавать им нужные размеры не боясь получить неожиданные отступы или пробелы.
Суть решения – установить родителю изображения display: flex. В нашем примере это будет ссылка. Фактически, мы говорим браузеру: "Хочу поставить два блока рядом, займись этим". Нелишним будет заметить, что строка текста в этом случае рассматривается как отдельный элемент (блок), но с такими деталями лучше ознакомиться в документации.

Проблема ушла!
Финал
Итак, после ознакомления с тем, как же браузер работает с изображениями и текстом, загадка перестаёт быть такой уж сложной, а проблема – неожиданной. Вам самим решать, какое решение применить в каждом конкретном случае. Главное помнить, что практически у любой проблемы в вёрстке есть логичное объяснение и решение. А скорее всего, даже не одно.
Подписывайтесь на канал Будни верстальщика чтобы не пропустить наши новые статьи и разоблачения мифов мира вёрстки.