Свойства для выравнивания всего и их новые тайны

Если вы освоили флексбоксы (а кто не освоил их за последние два-три года?), для вас давно не проблема отцентрировать что угодно, да и вообще выровнять как угодно. Вам давно знакомы «волшебные» свойства justify-content, align-content, align-items и align-self, и что делает каждое из их значений (не обязательно помнить их все наизусть, ведь всегда можно подсмотреть в шпаргалке или наглядном справочнике:). Но всё ли вы знаете про эти свойства? Что если я скажу вам, что их могущество не ограничено флексбоксами? И что вы видели лишь часть их значений? И что самих этих свойств не 4, а в два с лишним раза больше? Если хотите овладеть всей их мощью, узнать, причем тут новомодная грид-раскладка (CSS Grid Layout), и вас не страшат дебри спецификаций — добро пожаловать в эту статью, где я покажу вам, насколько глубока кроличья нора W3C.

CSS-гриды упомянуты не случайно: лучше сразу вооружитесь одним из браузеров, где они уже работают (Firefox 52+, Chrome 57+, Opera 44+ или Safari 10.1+/iOS 10.3+ Safari), чтобы увидеть примеры во всем их блеске.

Не только флексбоксы

В недавней статье про новые возможности флексбоксов и CSS-гридов, когда они работают в связке, был короткий пример со свойством align-self (и еще одним, но о нем чуть позже) для ячеек грида. А ниже — интерактивный пример, где вы можете сами сравнить действие всех четырех знакомых свойств в двух контейнерах с разными контекстами форматирования — флексбоксовом и гридовом:

See the Pen evKEMK by Ilya Streltsyn (@SelenIT) on CodePen.

Видите общий принцип?

Свойство justify-content определяет, что делать со свободным местом, оставшимся после размещения всего контента по горизонтали (точнее, по главной оси, зависящей от свойства flex-direction, для флексбоксов, и по строчной оси, зависящей от направления текста, для гридов — но пока ограничимся простым случаем, как в примере). Оставить ли это место в конце строки (по умолчанию), переместить в начало (прижав контент к концу), раскидать поровну справа и слева от контента (тем самым отцентрировав его), раскидать поровну между элементами и т.д. Единственная разница, что в гриде элементы сгруппированы еще и по вертикали (по столбцам), поэтому и место поневоле распределяется именно между столбцами, а не самими элементами, как у флексбоксов.

A align-content делает по сути то же самое, но по вертикали (точнее, по перпендикулярной оси для флексбоксов и по блочной оси для гридов, если совсем занудствовать). Здесь в обоих случаях контент у нас уже сгруппирован — в строки (во флексбоксах) или ряды (в гриде). И свободное место по вертикали может быть после всех этих строк/рядов, перед ним, поровну до и после них (и тогда они будут по центру), поровну между ними… Во флексбоксах (где, в отличие от гридов, нет ячеек с явными размерами) можно еще и равномерно растянуть высоту этих строк/рядов так, что свободного места не останется вообще.

Другими словами: свойства *-content управляют всем контентом сразу, передвигая и раздвигая (если надо) то, во что этот контент сгруппирован: строки, ряды или колонки.

Ну а align-items выравнивает именно «items», т.е. элементы — внутри строк флексбокса и рядов грида. А align-self — то же самое, но для каждого элемента в отдельности, и указывается для него самого. Первый задает поведение элементов по умолчанию, второй позволяет его переопределить.

Но как выравнивать элементы — все (по умолчанию) или некоторые по отдельности — по горизонтали, внутри колонок грида?

Целых девять свойств

Оказывается, для каждого объекта выравнивания — всего контента, всех элементов по умолчанию и отдельного элемента, его самого — есть пара свойств, одно из которых выравнивает по главной/строчной оси (в нашем примере — по горизонтали), а второе — по поперечной/блочной (в нашем примере — по вертикали). Получается 6 свойств — все комбинации из двух вариантов, по какой оси выравнивать, и трех — что именно выравнивать (контент, элементы или сам конкретный элемент). Вот они:

  • align-content
  • justify-content
  • align-items
  • justify-items
  • align-self
  • justify-self

See the Pen qrYobV by Ilya Streltsyn (@SelenIT) on CodePen.

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

Но писать целых два свойства для выравнивания каждого объекта долго, нельзя ли как-нибудь покороче? Не так давно (в конце осени 2016-го) рабочая группа CSS задалась тем же вопросом и решила ввести для каждой пары align-что-то/justify-что-то сокращенную запись. Над ее названием долго ломали голову и спорили, но в итоге победил вариант place-что-то.

Так что в придачу к тем шести свойствам теперь есть еще три:

  • place-content
  • place-items
  • place-self

Значение каждого из них — комбинация значений align-* и justify-* через пробел. Например, place-content: center space-between или place-self: stretch center. Порядок (сначала вертикальное выравнивание, потом горизонтальное) соответствует порядку, в котором указываются, например, отступы (в записях типа margin: 20px auto). Если указано только одно значение (например, place-items: start), оно копируется для обеих составляющих, по обеим осям.

Все 9 свойств уже работают, как минимум, в новых Firefox (52+). В Chrome до Canary 59 включительно сокращенные свойства (place-*) не работали, но (добавлено 25.05.2017) в Canary 60 они уже поддерживаются (хоть иногда и со странностями).

Куча новых значений

Если вы внимательно рассмотрели предыдущий пример, вас могли удивить некоторые значения для свойств выравнивания. Они действительно новые, и о них стоит рассказать отдельно.

start и end

Во флексбоксах мы привыкли к значениям flex-start и flex-end, зависящим от направления главной и поперечной осей — которые, в свою очередь, зависят от направления текста. Просто start и end — почти то же самое: начало/конец строки или блока (ряда или колонки) соответственно, в зависимости от направления текста. Для строки/ряда при письме слева направо start — это левый край, end — правый. При письме справа налево — наоборот. При вертикальном письме сверху вниз, соответственно, start — это верх, end — низ. И так далее.

На практике разница между start/end и flex-start/flex-end лишь в том, что первые, в отличие от вторых, не учитывают «переворота» осей в ситуациях типа flex-flow: row-reversed wrap-reversed. А в гридах они вообще по сути синонимы.

В Firefox эти значения уже работают и для флексбоксов, и для гридов, в Chrome — пока только для гридов.

self-start и self-end (для *-items и *-self)

Эти два значения хочется назвать «еще более относительными», чем просто start и end: они выравнивают флекс- и грид-элементы с учетом их собственного направления текста. Например, если в одних ячейках грида текст идет слева направо, а в других — справа налево (скажем, это русско-арабский словарь), то при justify-items: self-start содержимое первых прижмется к левому краю, а вторых — к правому. Вы могли видеть это в примерах выше — для предпоследнего элемента в каждом контейнере, выделенного зеленым цветом .

left и right

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

space-evenly (для *-content)

В переводе шпаргалки Джони Трайтел нам в своё время пришлось исправить одну неточность (в оригинале она осталась до сих пор, была она поначалу и в статье Криса Койера, от которой Джони отталкивалась). Для space-around было нарисовано, будто промежутки между элементами и от краев контейнера до крайних элементов равны друг другу:

ошибочный пример space-around, на самом деле так себя ведет space-evenly

Именно такого результата интуитивно ожидали многие (включая меня), и фактический результат space-around (промежутки между элементами вдвое шире, чем от краев) неприятно удивил. А равных промежутков до сих пор приходилось добиваться нетривиальными хаками — либо с помощью space-between и ::before c ::after, либо нетипичным применением margin: auto. И то лишь для однострочных флексбоксов.

И вот наконец у свойств justify-content и align-content появилось новое значение space-evenly, которое делает эти промежутки равными без всяких ухищрений. Хоть в однострочном флексбоксе, хоть в многострочном, хоть в гриде. Хоть по горизонтали, хоть по вертикали. И это уже работает в браузерах: в Firefox — полностью, в Chrome — пока только для гридов (добавлено 25.05.2017: но это был баг, и буквально позавчера его пофиксили! И в Safari тоже). Добавлено 24.07.2017: в Chrome Canary 62 это значение уже работает полноценно. Добавлено 10.08.2017: в стабильном Chrome 60 тоже!

first baseline и last baseline

Выравнивание по базовой линии издавна было сложной темой в CSS, даже величайшим мастерам порой приходилось вести с ним целые битвы. Особенно удивляло, что одни элементы — напр., инлайн-блоки — выравнивались по базовой линии последней строки, а другие — напр., инлайн-таблицы — по первой. Теперь этим можно управлять явно. Для этого перед ключевым словом baseline можно указывать другое слово-модификатор — first или last.

Обратите внимание: модификатор указывается именно через пробел, не через дефис, так что значение свойства получается составным, из двух слов. Из-за этого пробела чуть не возникла путаница с сокращенными значениями (как понимать, например, place-self:first baseline: как непонятное выравнивание first по одной оси и выранивание по базовой линии по другой или как выравнивание по базовой линии по обеим осям?), было даже предложение заменить в сокращенных значениях пробел слешем, но в итоге оставили пробел. Так что будьте внимательны!

(добавлено 25.05.2017) ключевые слова safe и unsafe

Их я пока не смог увидеть в действии ни в одном браузере, поэтому поначалу даже не хотел добавлять в статью. Но придется:)

Возможно, при центрировании элементов в резиновом флекс-контейнере вы сталкивались с такой неприятной особенностью: если контейнер становится меньше элемента, то оба края элемента начинают выступать за края контейнера. И если контейнер — это страница, то часть контента может уйти за ее левый/верхний край и стать недоступной. С этим и борется ключевое слово safe: если добавить его перед center (например, align-items: safe center;), то элемент будет центрироваться в контейнере лишь тогда, когда он там умещается. Если же он переполняет контейнер, то «лишние» части будут выступать лишь вправо и вниз (для привычного нам направления письма), где до них хотя бы можно будет добраться скроллингом. Примерно так, как ведут себя элементы при центрировании через margin: auto. Которое, кстати, и имитирует это поведение в примере ниже:

See the Pen OmqGKY by Ilya Streltsyn (@SelenIT) on CodePen.

Есть и противоположное ключевое слово unsafe — всегда выравнивать/центрировать элемент так, как указано, неважно, куда и насколько при этом выступают «излишки». А по умолчанию, по текущему черновику, должно происходить что-то среднее — элемент должен выравниваться как указано, но при появлении скроллинга он весь должен быть доступен для него (но там оставлена оговорка: если браузеры не осилят такое «умное» поведение — пусть делают unsafe:).

normal, auto и ключевое слово legacy

Как часто бывает в CSS, значения с названиями типа normal и auto оказываются самыми запутанными:). «Нормальное» поведение наших свойств для выравнивания всего зависит от конкретного способа форматирования, от унаследованных значений (модификатор legacy как раз влияет на то, будут ли эти унаследованные значения учитываться), и я даже не стал добавлять их в примеры (где было можно), чтоб совсем уж вас не запутать. Если захотите, разобраться подробнее с каждым из них вы сможете непосредственно в спецификации (см. ниже). К счастью, и во флексбоксах, и в гридах общий принцип поведения по умолчанию довольно прост: что можно — попытаться растянуть (stretch), остальное — прижать к началу соответствующей оси (start).

Целый модуль спецификации (CSS Box Alignment)

Свойства для выравнивания всего оказались настолько важны, что для них уже давно завели отдельный модуль спецификации: CSS Box Alignment Module Level 3. Работа над ним еще не завершена, но в официальном определении современного CSS (известном как «CSS-2017») он назван в числе «теоретически проработанных и вполне стабильных» (хоть и нуждается в дальнейших тестах и опыте внедрения). А актуальная его версия со всеми новейшими правками (текущий редакторский черновик) здесь: https://drafts.csswg.org/css-align/.

Модуль немаленький по объему и язык в нем, прямо скажем, не самый простой. Чтобы просто понять, какие значения могут быть у каждого свойства, приходится побегать по перекрестным ссылкам: что такое, например, «<self-position>» и чем оно отличается от «<content-position>»? Ответ — то, что среди значений для выравнивания отдельных элементов, в отличие от значений для выравнивания групп, есть наши новые знакомые self-start и self-end — вполне логичен, но, чтобы понять эту логику, надо как следует вчитаться! Неудивительно, что по этому модулю до сих пор немало открытых иший. Так что советую поизучать его повнимательнее — вдруг именно от вас не ускользнет какая-нибудь важная неточность, а то и возможность переформулировать что-нибудь попроще и пояснее?

Но самый главный сюрприз, который скрывает в себе этот модуль — то, что свойства для выравнивания всего не ограничиваются одними лишь флексбоксами и гридами: в теории, они должны будут работать для многоколоночных раскладок (кстати, совсем недавно браузеры — Chrome с 55-й версии, Firefox c 52-й — наконец убрали префиксы для колоночных свойств!) и… барабанная дробь… для обычных блоков! Так что, если я ничего не перепутал, align-content: center должно стать стандартным решением легендарной проблемы вертикального центрирования произвольного контента. К сожалению, для justify-content для блоков явным образом прописано, что применяться оно не будет (эх, придется и дальше неинтуитивно центрировать margin-ами…), но для align-content, в теории, надежда остается! В теории — потому что в браузерах (ни в FIrefox, ни в Chrome Canary) это пока, судя по всему, не работает.

Вообще браузерная поддержка свойств для выравнивания всего — больная тема: на CanIUse отдельной закладки для CSS Box Alignment нет, в MDN, хоть информация о поддержке разных значений на удивление подробная, упоминаются они пока лишь в контексте флексбоксов. Так что, видимо, единственный способ узнать всё обо всех нюансах этих могучих свойств во всех, включая самые неожиданные, ситуациях — это экспериментировать, экспериментировать и еще раз экспериментировать (держа перед глазами спецификацию и, если надо, спрашивая обо всём непонятном прямо у ее редакторов на гитхабе:). Так что жду в комментариях поправок и уточнений ко всему, что я упустил (я ведь даже не все возможные значения перечислил), и, разумеется, ваших собственных открытий. И да пребудет с вами сила CSSпецификаций!

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

11 Комментарии
  1. БобрДобр

    Фраза «Тяжело в учении, легко в бою» очень хорошо подходит к гридам. Спасибо за перевод.

    1. SelenIT (Автор записи)

      Упс… вообще-то это задумывалось как «оригинальное исследование» — именно потому, что подробных материалов по свойствам Box Alignment я не смог найти даже на заграничных ресурсах (не считая самой спецификации:). А вы видели аналогичный материал на английском? Буду очень благодарен за ссылку!

      1. БобрДобр

        Оу, я почему-то решил, что это перевод. Тогда ещё большее спасибо за статью.

  2. c01nd01r

    Большое спасибо, отличная статья!

  3. NMitra

    Я тихо сползаю под стол :)

    С гридами делаю наугад. Почему всё расползается, когда использую дважды fr для меня вообще загадка.

    1. Максим Усачев

      Чтобы пришло какое-то начальное понимание гридов, попробуйте внимательно прочитать наши статьи (все три части) по теме. Там всё это описывается подробно и с примерами.

      1. NMitra

        Начальное понимание уже есть :)

    2. SelenIT (Автор записи)

      Гриды еще относительно новая технология, поэтому это вполне может быть баг браузеров, а то и спецификации. Давайте устроим мозговой штурм. Нет ли ссылки на проблемный пример?

      1. NMitra

        Я уже не помню детали, но сейчас не получается воспроизвести. Спасибо за предложение! В следующий раз вооружусь примерами :)

  4. Павел Радьков

    Спасибо за статью! Уже пару лет верстаю на флексбоксах, но снова и снова приходилось заглядывать в шпаргалку, т.к. запомнить наизусть не мог.
    Теперь уяснил для себя, justify-* — это всегда «продольные колебания», а align-* — «поперечные колебания». А у «align-item» и «align-self» есть логические пары, без которых картина на складывалась.

    1. SelenIT (Автор записи)

      justify-* — это всегда «продольные колебания», а align-* — «поперечные колебания».

      Именно так! Отлично сформулировано! :)

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

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

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

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