CSS-live.ru

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

Если вы освоили флексбоксы (а кто не освоил их за последние два-три года?), для вас давно не проблема отцентрировать что угодно, да и вообще выровнять как угодно. Вам давно знакомы «волшебные» свойства 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

Предполагались для случая, когда, хоть и редко, возникает необходимость выровнять что-то и абсолютно, по правому/левому краю, независимо от направления текста. Как эти свойства должны работать для вертикального выравнивания (и нужны ли они там вообще), редакторы спецификации пока не определились (на 9.01.2018).

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. Это тоже может быть интересно:

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

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

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

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

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

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

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

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

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

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

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

    2. Да, самое главное, чтоб картина сложилась, чтоб увидеть в этом во всём какую-то логику, позволяющую реализовать какие-то возможности и тогда это обилия свойств будет не сбивать с толку, а наоборот, окажется удобным инструментом, на все случаи жизни :)

  4. Статья понравилась, выполнена в виде обзора, из которого я понял, что не смотря на обилие свойств, выравнивать будет проще. Логика выравнивания в начале статьи, объяснена доходчиво, поэтому и за всеми последующими рассуждениями, мысль вполне улавливается.
    Но на самом деле, интересно посмотреть как это на практике будет работать, и насколько подобная раскладка может позволить гибко решить любую задачу и удобно ли будет осуществить те, или иные, тонкие настройки…
    Вот простой (а может и не очень) пример. У меня есть три элемента. Мне нужно, чтоб при сужении окна, они последовательно складывались один под другой, желательно, чтоб вертикальные марджины между ними схлопывались. А при расширении, крайние элементы прижаты к краю, но контент у одного из них(например у правого), чтоб постепенно увеличивался отступ до контента от края. Контент же среднего элемента, при больших разрешениях, левый край контента начинался на какую-то абсолютную или относительную величину в право или влево от центра. При этом, когда окно обратно сжималось, оно чувствовало соседние элементы и, аккуратно сжималось до боковых марджинов (ну или gap’ов, не суть), ну а дальше складывалось, как я писал выше.
    Вот эту задачу хочу решить несколькими способами, инлайн-флоатно я её уже практически решил, флексами попробовал, оказалось что высчитывать абсолютное расстояние, в случае justify-content: space-between значительно сложнее, чем когда ты его просто определяешь, но, прочитав Вашу статью, я начал понимать, что тут, по-видимому нужно подумать над *-self свойствами, не исключено, что найду и более лёгкое и изящное решение, чем инлайн-флоатное(иначе смысл заморачиваться с новыми раскладками?). Ну а гриды пока смотрел ещё более поверхностно(хотя всякие грядки в песочницах поливал), но там не вполне было понятно, как сделать так, чтоб элементы, при сужении окна, складывались друг под друга.
    Посмотрю, если эти детские проблемы, удастся решить легко и непринуждённо, то, вероятно, буду применять эти раскладки чаще, и с большей охотой :-)

    1. Спасибо за отзыв! У нас как раз только что появился своего рода «сиквел» этой статьи — http://css-live.ru/articles-css/box-alignment-guide-cheatcheet.html, надеюсь, они будут дополнять друг друга).

      Насчет задачи — можно ли уточнить, как должна вести себя ширина элементов при расширении окна? Возможно, если выбрать гриды, складывание удастся реализовать через auto-fit (а к правому краю прицепиться через grid-column: -1). Ну и всегда остается вариант смены структуры грида по media query…

      1. «Насчет задачи — можно ли уточнить, как должна вести себя ширина элементов при расширении окна? »
        Вот тут как раз тот случай, когда не так важна ширина контейнеров, важно, чтоб контейнеры позволили расположиться им, так как им удобно( с нашей точки зрения ;) ). Что-то вроде justify-items/self…
        Контент не расширяется в зависимости от ширины окна, тот, что слева, просто слева, справа — там у него растёт отступ с права, а по центру, когда он может позволить себе достаточный -self должен расположить свой левый край на определённую величину(абсолютную или относительную) слева от центра. Ну или справа, главное чтоб было понятно как его левый край двигать относительно центра блока (впрочем можно для разнообразия попробовать двигать центр контента или его правый край)…
        ЗЫ: напишите, пожалуйста, какие теги можно использовать в сообщении, а то всё-таки выделять цитирование кавычками в вебе,, есть же получше средства…

      2. Вдогонку: кстати, вполне возможно, озвученная выше задача, вполне неплохо решается через гриды, но практически не решается через flex-боксы, именно потому, что justify-items/self свойства там не работают.

      3. Возможно, если выбрать гриды, складывание удастся реализовать через auto-fit (а к правому краю прицепиться через grid-column: -1). Ну и всегда остается вариант смены структуры грида по media query…

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

        Не очень понял Вашу идею, но, в любом случае auto-fit, насколько я понимаю, это одинаковые колонки, а мне нужно, грубо говоря, одна колонка — по контенту, вторая, вероятно, подойдёт fr, а третья — процент. И чтоб контент внутри колонки можно было перемещать и контролировать отступы. (собственно, ради контролирования места контента в общем контейнере всё и затевается).

        Ну а складываться они должны по-контенту. Правда я не совсем понимаю, похоже в гридах, ситуация, когда один большой элемент останется сверху, а внизу окажутся два маленьких, при условии, что каждый элемент, по-умолчанию, занимает одну «ячейку» — не реальна?

        В общем, вот гипотетический пример: https://jsfiddle.net/xoazeqnw/3/ — как его сложить? (при уменьшении ширины окна)

        Во-флексах проблема, насколько я помню, решается элементарно, там, кстати, есть ещё выравнивание соседних элементов по базовой линии, чего я не нашёл в гридах, но там другая засада, нет свойства justify-self. А без него расположить, те или иные, контент-элементы, там где мне нужно, довольно проблематично, на автомате они располагаются красиво, но если хочешь большего, то, скорее всего, решить можно, но, вероятно, решение будет неоправданно громоздким…

      4. Насчёт задачи:

        https://jsfiddle.net/Launder/a97gsme9/125/ — двухкомпонентная шапка

        https://jsfiddle.net/Launder/a97gsme9/125/ — трёхкомпонентная

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

        В гридах основаная проблема — чтоб складывается по сетке, а не по контенту.

        Во-флексах — складывается нормально, а вот когда растягивается, распределяет всё сам, хотелось бы точности…

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

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

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