CSS-live.ru

Удивительный и неизвестный 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 отвечает за две вещи (на самом деле не только, но пока хватит этого):

  1. Внешнее поведение элемента — как он взаимодействует с соседями, т.е. в каком контексте форматирования он сам находится (или, как говорит спецификация, участвует);
  2. Внутреннее поведение элемента — как он влияет на размещение своих потомков, т.е. какой контекст форматирования действует в нем самом.

Для каждого поведения теперь есть отдельный набор ключевых слов, так что значения свойства 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. А где его нет — подбирайте наиболее подходящий для вас вариант из сравнительной таблицы (мой фаворит — column-count: 1;).

Хак: запрет переноса строк в колонках

В многоколоночной раскладке по умолчанию блоки могут разрываться и переноситься в новую колонку по частям. Обычно так и надо, но иногда нужно какой-то блок перенести только целиком. Есть давний хак, когда для этого таким блокам меняют display на inline-block. Теперь вы знаете, благодаря чему он работает: ведь для блочного контекста весь анонимный блок с инлайн-блоком внутри — это одна строка!

Хак: выравнивание флекс- или грид-элементов по последней строке заголовка

Бывают задачи, которые пока не под силу даже могучим свойствам для выравнивания всего, но решаются благодаря уникальной базовой линии инлайн-блоков. Иногда они могут почти заменить подсетки:

See the Pen MBmwRw 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ила! :)

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

16 комментариев

  1. В примере выравнивания по верху заголовков, долго думал, пока не задал цвет блокам и заголовкам.

    Кстати, у вас говорится про особенное поведение при vertical-align: baseline; так же выравнивание происходит по последней строке при super и sub, но не при top и bottom.

  2. А разве не потому что inline-block, это атомарный элемент строки, которые являются неделимыми? Или я не так понял суть? В многоколоночной ведь перенос не по строкам, а по словам.

    Теперь вы знаете, благодаря чему он работает: ведь для блочного контекста весь анонимный блок с инлайн-блоком внутри — это одна строка!

    1. Думаю, можно сказать и так). Неделимый элемент строки может занимать только один контейнер строки, а один контейнер строки должен целиком уместиться в одну колонку (либо остаться в предыдущей, либо целиком же перенестись в следующую). Я хотел подчеркнуть разницу между инлайн-блоком и обычным блоком (которые визуально могут выглядеть идентично), а эта разница – как раз в анонимной блочной обертке с единственным контейнером строки, содержащим неделимый элемент (даже если визуально в нем много строк текста!).

  3. Ещё одна странность. если внутри инлайн-блока только пробел, то этот пробел не будет занимать никакой ширины :)

    <span>1</span><span> </span><span>2</span>

    этот код вернёт 12 но не 1пробел2

    1. Это логично, ведь внутри у инлайн-блока блочный контекст форматирования, в котором пробелы не учитываются.

  4. а почему бы стрелочку на сайт не поместить, что бы так в конце статьи (вдруг захотелось что то перечитать вверху) нажал на нее и вжух, ты в самом начале, а так приходится колесико мышки упорно крутить :)

  5. Недавно обнаружил Ваш сайт, очень содержательные статьи, узнаю много нового, так как пока только начинающий разработчик. Например, есть непонятный момент вот здесь:

    https://codepen.io/mblandr/pen/VwaKgMw

    откуда внешний div берез свою высоту? Подскажите, пожалуйста!

  6. Благодаря таким статьям сайто-строение становится более понятным и можно решать многие вопросы без программистов. Я не разработчик. Мое дело — маркетинг и Seo. Однако, у клиентов не всегда есть свои разработчики. Из-за этого иногда приходится лезть в код, чтобы внести небольшие правки, не отдавая работу на аутсорсинг. Благодаря информации, которой вы открыто делитесь, многие проблемы решаются своими силами. Спасибо вам большое за труд!

Добавить комментарий для Tahir Отменить ответ

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

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