Удивительный и неизвестный inline-block
Эта статья задумана как начало цикла про загадки, сюрпризы, малоизученные особенности и маленькие полезные секреты самого могущественного, пожалуй, свойства в CSS — свойства display
. У нас уже были статьи про новинки этого свойства — значения contents
и flow-root
. Но и старые, давно знакомые (казалось бы) значения этого свойства таят в себе немало сюрпризов, подводных камней и удивительных открытий. Прежде всего, пожалуй, это значения с «приставкой» inline-
: inline-flex
, inline-grid
, inline-table
, и, наконец, наш старый знакомый inline-block
.
С него-то мы и начнем наш экскурс в фундаментальные глубины CSS образца 2018 года, в конце которого у нас должно сложиться целостное представление обо всех существующих механизмах раскладки в браузере. Готовы к сюрпризам?
Чуть-чуть истории
Для молодых верстальщиков и фронтендеров инлайн-блоки — что-то очень древнее. Но ни в CSS1, ни в CSS2 никакого inline-block
не было. Появился он только в CSS2.1, а стандартом CSS2.1 стал лишь в 2011-м, в эпоху «CSS3». В Firefox до 3-й версии (а версии тогда «жили» не по полтора месяца, а годами) inline-block
приходилось эмулировать через -moz-inline-box
, который был не чем иным, как… мозилловской реализацией старой версии флексбоксов (да-да, инлайн-блоки эмулировали флексбоксами, вот так ирония!). Как ни странно, IE это значение поддерживал (в нем оно впервые и появилось!), но… только для элементов с дефолтным inline
(впрочем, это ограничение обходилось простым хаком). В общем, веселое было время:)
В начале 2010-х инлайн-блоки пережили всплеск популярности как основа для сеток. Они давали намного больше простора для разных вариантов выравнивания, чем надоевшие флоаты, и при этом не нуждались ни в каких «клиарфиксах». И по сей день они — один из самых популярных фолбэков для нормальных систем раскладки (флексбоксов и гридов).
Но как и флоаты, инлайн-блоки не были предназначены для раскладки блоков, так что своих хитрых хаков и тут хватало.
Не inline
и не block
Во многих учебниках и руководствах для новичков про inline-block
пишут, что он «строчный и блочный одновременно». Это неправда.
Inline-block
— не inline-элемент (который может разрываться между строками, для которого вертикальные padding
-и и border
-ы не влияют на его положение, который игнорирует width
и height
и т.д.). И не блочный элемент (который пытается заполнить всю доступную ширину контейнера, margin
-ы которого схлопываются с margin
-ами соседей и даже потомков, в который могут вторгаться флоаты и т.д.). У него есть отдельные черты того и другого, но…
Пожалуй, проще всего понять, что такое inline-block
на самом деле, обратившись к новой спецификации CSS Display 3 уровня.
Постоянные читатели нашего сайта уже знают, что по новой спецификации свойство display
отвечает за две вещи (на самом деле не только, но пока хватит этого):
- Внешнее поведение элемента — как он взаимодействует с соседями, т.е. в каком контексте форматирования он сам находится (или, как говорит спецификация, участвует);
- Внутреннее поведение элемента — как он влияет на размещение своих потомков, т.е. какой контекст форматирования действует в нем самом.
Для каждого поведения теперь есть отдельный набор ключевых слов, так что значения свойства display по новому стандарту будут составными. А старые значения с дефисом будут псевдонимами, алиасами для некоторых из таких комбинаций (говорю «будут», потому что браузеры еще не понимают составных значений, но в стандарт это уже заложено).
Какому же составному значению будет псевдонимом наш inline-block
? Думаете, «inline block» («инлайновый снаружи, блочный внутри»)? Вот и нет! На самом деле его полное значение вот какое:
inline flow-root
Иначе говоря, он участник инлайнового контекста снаружи и начало нового блочного контекста форматирования внутри. Неожиданно! Но логично.
В инлайновом контексте форматирования ничего блочного быть не может. По построению. Как только в контейнере появляется что-то с display:block
, контейнер по волшебству — то есть по стандарту — превращается из контейнера текста в контейнер блоков, и у нового блока появляются анонимные блочные соседи. Поэтому, чтобы вставить блочное содержимое в инлайновый контекст, нужен некий «переходник». Начало блочного контекста форматирования, ведущее себя как элемент строки — это он и есть. По-видимому, редакторы спецификаций сами поняли это только с появлением самого flow-root
. Из-за этого «запоздалого прозрения» пришлось спешно решать еще одну проблему… но о ней чуть позже, в продолжениях.
То, что он сам участвует в инлайновом контексте — ключевая особенность инлайн-блока. Из нее вытекают и многие его преимущества, и почти все раздражающие недостатки. Они, кстати, будут и у inline-table
, inline-flex
и inline-grid
, так что это знание пригодится во многих ситуациях.
Атомарный элемент строки
За одним редким исключением (о нем в другой раз), все «переходники» между инлайновым и каким-либо другим контекстами форматирования ведут себя как сплошные прямоугольники, при этом встроенные в строку текста, как буквы или эмодзи. Кстати, так же себя ведут и «переходники» между текстом и чем-то, вообще не относящимся к CSS — замещаемые элементы (картинки, видео…) и виджеты, как их называют в HTML5. CSS называет такие неделимые прямоугольники атомарными элементами строки (atomic inline boxes).
Ключевой момент здесь то, что это элементы строки. А значит, подчиняются непростым для понимания правилам инлайнового контекста:
See the Pen
Приключения инлайн-блоков в строчном контексте форматирования by Ilya Streltsyn (@SelenIT)
on CodePen.
Вот несколько особенностей, которые, по моим наблюдениям, вызывают больше всего проблем и недоразумений на практике.
Минимальное пространство по высоте
Любой инлайн-блок, даже самый маленький, заставляет браузер использовать целый контейнер строки, минимальная высота которого определяется свойством line-height
родителя. Увеличить (распереть) эту высоту инлайн-блок может, но уменьшить — нет. А поскольку высота строки складывается из двух частей — над базовой линией и под ней, возможна ситуация, когда инлайн-блок «распирает» верхнюю часть, «отпихивая» собой базовую линию от «потолка» контейнера строки (как пустой инлайн-блок в нашем примере), но пространство под ним остается! Даже если крошечный инлайн-блок будет единственным потомком родителя, он всё равно займет целую строку.
Так что, если у крошечной кнопки или иконки вдруг появились «непонятные отступы», которых не видно даже в браузерном отладчике (!), просто вспомните про эту особенность. Побороть ее можно, задав контейнеру очень маленькую line-height
(вплоть до 0
). Но лучше сразу использовать для таких вещей не инлайн-блоки, а обычные блоки или флексбоксы — там ни с чем бороться не надо.
Пробелы имеют значение
Про эту «проблему», думаю, читатели нашего сайта давно знают. Но упомянуть ее надо. Коротко: в инлайновом контексте любые пробельные символы подряд (сами пробелы, табуляции и переводы строки, в т.ч. между тегами) внутри текста, при дефолтном white-space: normal, отображаются на экране как один пробел. В примере выше видно, почему это не баг: ведь инлайн-блоки вписываются в текст. А значит, отделяются пробелами от соседних слов на общих основаниях и прижимаются вплотную к знакам препинания и другим символам, если надо. Если нужно другое поведение, лучшее решение — использовать что-то другое. Не инлайн-блоки, а, например, те же флексбоксы.
Здесь же стоит упомянуть, что по умолчанию инлайн-блок может переноситься на новую строку, даже если перед ним нет пробела. Так сложилось исторически. Если нужно «приклеить» инлайн-блок к соседнему слову, чтоб они переносились только вместе (например, иконку к ссылке), можно, например, обернуть их в общий <span>
с white-space: nowrap
.
«Странное» действие middle
В последней строке примера видно, что второй инлайн-блок, с vertical-align: middle
, не выровнен по середине контейнера строки, а стоит гораздо ниже. Можно подумать, что vertical-align
вообще не сработало, но если присмотреться, видно, что текст в нем на пиксель ниже общей базовой линии.
Разгадка в том, что middle
выравнивает середину высоты инлайн-блока с серединой высоты строчных букв текста родительского элемента. Т.е. ставит ее на 0.5ex
выше базовой линии. Середина контейнера строки с этой точкой обычно не совпадает. Поэтому такое выравнивание сдвигает инлайн-блок и, как часто бывает в строчном контексте, может дополнительно «распереть» контейнер строки и вызвать еще один непонятный отступ над (или под) элементом. Будьте начеку!
Уникальная базовая линия
Эту особенность я даже вынес в отдельный раздел, потому что она (на сегодняшний день) уникальна именно для инлайн-блоков: их базовой линией, по которой они выравниваются относительно окружающего текста, считается базовая линия последней строки текста внутри них. Это видно на примере «инлайн-блока в две строки» выше. У элементов с другими display: inline-что-то
(о них мы позже поговорим отдельно) базовая линия берется не по последней, а по первой строке. Чуть ниже мы увидим, как эта уникальность может нам пригодиться.
Важный нюанс: если контента в инлайн-блоке нет, то базовой линией считается нижняя граница его внешнего отступа (см. последний инлайн-блок в примере). Если его overflow
не равно visible
— тоже. Так что если под пустыми или обрезанными инлайн-блоками появляется больше пустого места, чем под непустыми — это не баг (если мешает, можно просто поменять vertical-align
). Уникален ли этот нюанс для инлайн-блоков, или другие display: inline-*
должны его перенять — было неясно, но (добавлено 22.08.2018) оказалось, что да, это тоже исключение из правила.
inline-block
в роли блока
И всё же многим атомарный элемент строки очень похож на блок. У него есть все элементы боксовой модели (margin
/border
/padding
), и все они работают предсказуемо, увеличивая занимаемое элементом место (как мы опять же видели в примере). У него есть размеры. В частности, ему можно задать width: 100%
(минус боковые padding
-и, если надо) — и он растянется на всю доступную ширину контейнера. И внутри у него может быть полноценное блочное содержимое — заголовки, абзацы, даже флоаты и таблицы (если надо). Чем не блок? Но всё же существенная разница есть.
Неуправляемая анонимная блочная обертка
Контейнер строки, в котором живет инлайн-блок и который нельзя сделать ниже определенного минимального размера, никуда не девается. И если рядом с этим контейнером строки оказывается что-то блочное, то он оборачивается в анонимный блок. То есть на самом деле у нас получается этакая «матрёшка»: снаружи анонимный обычный блок (с параметрами по умолчанию — на всю доступную ширину родителя, отступы по нулям и т.п.). В нем — строка «виртуального» текста, со свойствами, унаследованными от родительского контейнера. Внутри этой строки — атомарный элемент на всю ее ширину. И только внутри него — отдельный блочный контекст форматирования со своим блочным содержимым.
Анонимный блок всегда продолжает родительский контекст форматирования, поэтому «ужиматься», чтобы разместиться рядом с флоатом, не умеет. Сам инлайн-блок — атомарный элемент строки, как длинное неразрывное слово — тоже. Поэтому, если контента в нем много, инлайн-блок будет стремиться заполнить всю ширину родителя, а не только свободное место рядом с флоатом. Это еще одно важное отличие инлайн-блока от обычного, блочного flow-root.
See the Pen
Сравнение обычного flow-root и инлайн-блока (inline flow-root) в блочном окружении by Ilya Streltsyn (@SelenIT)
on CodePen.
Много строк внутри — одна строка снаружи
Еще раз отметим, что с точки зрения родительского блочного контейнера инлайн-блок в своей анонимной блочной обертке считается единственной строкой «призрачного» текста. В некоторых случаях это бывает полезно (см. ниже).
Полезные применения inline-block
Тривиальное
Прямое назначение инлайн-блоков — вставка чего-то «блокоподобного» (кнопки, текстовые иконки, теги, беджи и т.п.) прямо в текст.
See the Pen
wxdavy by Ilya Streltsyn (@SelenIT)
on CodePen.
Хак: фолбэк для флексбоксов (и гридов)
С оговорками, но всё же у текста в CSS есть аналоги многих возможностей флексбоксов. Включая многие виды горизонтального (влево, вправо, центр, по ширине) и вертикального (по базовой линии, по верхнему или нижнему краю контейнера строки…) выравнивания. Поэтому инлайн-блоки издавна используют как своего рода «флексбоксы для бедных».
Но они не владеют главным «волшебством» самого свойства flex
(автоматический размер) и не умеют подстраиваться к высоте соседей. И, наверное, не стоит тратить время и силы, пытаясь воспроизвести в фолбэке все красоты современного решения. Если в ископаемом браузере, который к тому же наверняка работает на ископаемом же «железе», блоки просто выстроены по горизонтали в цепочку, и макет не разваливается — его неизбалованному пользователю этого достаточно.
Хак: еще одна альтернатива клиарфиксу
Как любой элемент, создающий блочный контекст форматирования, inline-block
не выпускает наружу ни «плавающих» потомков, ни margin-ов обычных, всегда охватывая их по габаритам. Поэтому его в принципе можно использовать «вместо клиарфикса». Но помните об издержках неуправляемой анонимной обертки. И лучше используйте «нормальный», блочный flow-root
.
Хак: запрет переноса строк в колонках
В многоколоночной раскладке по умолчанию блоки могут разрываться и переноситься в новую колонку по частям. Обычно так и надо, но иногда нужно какой-то блок перенести только целиком. Есть давний хак, когда для этого таким блокам меняют display
на inline-block
. Теперь вы знаете, благодаря чему он работает: ведь для блочного контекста весь анонимный блок с инлайн-блоком внутри — это одна строка!
Хак: выравнивание флекс- или грид-элементов по последней строке заголовка
Бывают задачи, которые пока не под силу даже могучим свойствам для выравнивания всего, но решаются благодаря уникальной базовой линии инлайн-блоков. Иногда они могут почти заменить подсетки:
See the Pen
выравнивание по низу заголовка на примере каталога ненужных товаров by Ilya Streltsyn (@SelenIT)
on CodePen.
Допустим, маркетолог вот такого каталога требует, чтобы товары в нем были непременно выровнены по низу заголовков (или по верху описаний, что то же самое). Первое, что приходит на ум — таблица, либо грид с разбивкой заголовка и текста в разные грид-элементы. Но если сделать весь header
каждого товара инлайн-блоком, этот инлайн-блок окажется единтственным содержимым первой как-бы-текстовой строки, и ее базовая линия пройдет по последней строке этого инлайн-блока. Теперь достаточно выровнять сами товары по этой базовой линии, т.е. align-items: baseline
— и вуаля.
Примечание: к сожалению, в Firefox с выравниванием в гриде нашелся странный баг. Я долго не мог воспроизвести его отдельно, потому что задавал заголовку явную ширину (чтоб форсировать перенос на новую строку), а оказалось, что эта ширина (не в процентах, и не больше 100% по факту) тот баг… почему-то фиксит:). Возможности метода это изрядно ограничивает. Добавлено: похожая проблема в iOS Safari, но там даже «фикс» не помогает:(
Зато со флексбоксами никаких подобных проблем пока не нашлось, что радует.
Миф об «инлайн-блочных элементах в HTML»
Напоследок — небольшой курьез. С развитием CSS старинное деление HTML-элементов по внешнему виду, на «блочные» и «строчные», стало порождать домыслы о том, что другим видам CSS-отображения тоже могут соответствовать какие-то свои категории контента в HTML. Так, видимо, родился миф об «инлайн-блочных элементах в HTML». Чаще всего в таковые «записывают» элемент <button>
, реже <img>
и др. Так вот, это ерунда.
В HTML не было и нет такой категории элементов, и выделять ее нет смысла, полезной информации она не даст. Классифицируйте элементы по их логической роли: структурные, заголовочные, текстовые, интерактивные и т.д., как это делают современные стандарты. Эта классификация сразу даст вам подсказку, для чего элемент может использоваться (например, структурный — для разметки крупных логических блоков страницы, чтобы пользователю скринридера было удобно легко переходить между ними, поэтому их не должно быть на странице слишком много) и что в него можно вкладывать (скажем, интерактивные элементы обычно нельзя вкладывать друг в друга, чтобы браузер не запутался, чьё действие выполнять). А внешний вид любому (ну, почти) элементу можно придать любой — с помощью CSS. В том числе и вид инлайн-блока.
В CSS — и в этой статье — термины «блок» и «строчный элемент»/«элемент строки» относятся исключительно к внешнему виду, и могут относиться вообще не к элементу DOM, а к псевдоэлементу. Общего с устаревшей классификацией элементов из HTML 4 у них не больше, чем у JavaScript и Java. Не дайте себя запутать, и да пребудет с вами CSSила! 🙂