Красивое выравнивание блоков по резиновой сетке. По-новому

Почти пять лет назад на нашем сайте вышла статья о том, как выстроить некие однотипные блоки (товары в каталоге, фото в галерее…) по сетке, оптимально вписанной в доступное пространство. Сейчас она — одна из самых популярных на сайте, и число ее просмотров всё растет. Ведь актуальность задачи никуда не делась. Самое время вернуться к этой теме и раскрыть ее по-новому.

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

Да, то решение пятилетней давности было хаком. Да, с тех пор многое изменилось, даже рендеринг шрифтов в браузерах (появилось субпиксельное сглаживание, а с ним — дробные пиксели в координатах фрагментов текста, отчего метод стал чаще капризничать). Тем удивительнее, что альтернатив этому хаку так и не появлялось! Флексбоксы, при всей их мощи, не одолели главный камень преткновения той задачи — выдержать горизонтальный ритм в последней строке. Практически единственный способ решить это во флексбоксах — добавить в конец списка кучу невидимых несемантичных блоков-распорок нужной ширины, как в совсем древние времена.

Но теперь у задачи наконец появилось красивое, надежное, семантичное, а главное — стандартное решение. Не без нюанса, как водится — но это же веб, ребята;)

Итак, встречайте…

Красивое и правильное выравнивание блоков по резиновой сетке (версия 2017 г.)

Вы наверняка догадались, что это решение — использовать CSS-гриды. Но хватит уже вступлений, давайте смотреть пример:

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

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

Какие секретные ингредиенты тут использованы

Никаких! Только натуральные стандартные возможности CSS Grid Layout. Причем по прямому назначению.

А несекретные?

Повторение грид-колонок функцией repeat() со значением auto-fill (или auto-fit… но с ним как раз есть нюанс:). Оно отвечает за три главных задачи — построение сетки, ее адаптивность и горизонтальный ритм. Ряды создаются автоматически по необходимости — это поведение грид-раскладки по умолчанию. Ну и свойство justify-content (нам уже знакомое) наводит последние штрихи.

В большинстве статей по гридам, что мне пока встречались — да и в самой спецификации — грид с динамическим числом колонок, заполняющих контейнер, иллюстрируется примерами кода типа grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));. Это значит, что «излишек» ширины контейнера (остаток от деления его ширины на минимальную ширину колонки — в данном случае на 100px) равномерно распределится между колонками, и при увеличении ширины контейнера ширина колонок будет колебаться: в 250-пиксельном контейнере будут 2 колонки по 125px, в 298-пиксельном — 2 по 149px, а в 303-пиксельном — уже 3 колонки, но всего по 101px каждая. Мы видели такое и во флексбоксах. Но сейчас нам гибкая ширина колонок не нужна, ведь по условию задачи мы делаем сетку из блоков известного размера (по крайней мере, не больше известного размера). Поэтому пишем еще проще: grid-template-columns: repeat(auto-fill, 100px);. Остаток ширины контейнера остается никак не распределен — но с этим блестяще справится justify-content!

Свойство justify-content может распределять излишек ширины поровну по бокам сетки (центрируя ее), поровну между колонками, поровну между краями контейнера и колонками, а также вокруг каждой колонки (справа и слева). Соответствующие значения — сenter, space-between, space-evenly (новинка сезона!) и space-around. В примере показаны первые три, как самые полезные (по-моему:).

Итого, вся задача по сути решается тремя строчками CSS:

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, 100px); /* или repeat(auto-fit, 100px); */
  justify-content: center; /* или space-between, или space-evenly, можно и space-around */
}

Немного про auto-fit и загадочный «нюанс»

Сюрпризом обернулась попытка «подружить» justify-content и auto-fit (вместо auto-fill). По спецификации, auto-fit вначале использует тот же алгоритм, что auto-fill, но потом «скукоживает» до нуля все колонки, котрорые остались пустыми (им не досталось грид-элементов). Более того — если между ними были грид-интервалы, то эти интервалы тоже накладываются друг на друга, что получается фактически один интервал. А для крайней колонки, у которой интервал только с одной стороны, он вообще не учитывается. То есть, фактически, в обычных условиях этих «скукоженных» колонок всё равно что нет. А есть только те колонки, в которых реально есть элементы.

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

Но при распределении места между колонками браузеры повели себя по-разному. Firefox равномерно распределил по контейнеру ячейки, заполненные элементами, а вот Chrome и Safari оставили справа от них какую-то странную пустоту.

В поисках ответа я обратился к Мануэлю Рего Касановасу (чьи статьи о гридах мы часто переводили): кто лучше объяснит странность реализации, чем ее разработчик? И Мануэль указал мне на проблему в спецификации: она, как оказалось, ничего не говорила о том, как align-content и justify-content должны вести себя со «скукоженными» грид-полосами. И реализация в Chrome и Safari формально следовали букве спецификации: раз грид-полоса есть, хоть и нулевая — при распределении контента тоже надо ее учесть. А Firefox следовал ее духу: раз с грид-полосой специально что-то сделали, чтоб она не была видна и вообще никак не проявлялась — значит, и тут учитывать ее не надо.

Лично я здесь целиком на стороне Firefox. На мой взгляд, если уж убирать ненужные полосы визуально, так убирать их везде. В ишье на гитхабе, которую я открыл по этому поводу, редакторы спецификации Таб Аткинс и Элика Этемад (fantasai) согласились: Firefox ведет себя как задумывалось, а поведение Chrome и Safari — ошибка. Теперь спецификация уточнена (буквально на следующий день, спасибо редакторам за оперативность!), так что браузерные реализации тоже вскоре должны привести к единому правильному виду. Добавлено 26.07.2017: в Chrome Canary 62 баг уже исправлен!

Вместо заключения

Мы нашли стандартное решение старой, но по-прежнему актуальной задачи. На мой взгляд, можно смело улучшать им существующую верстку, обернув в @supports (как советует Эрик Мейер), или внедрять как основное в новые проекты, оставив старый хак в качестве фолбэка.

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

В общем, смелей экспериментируйте с гридами, польза от этого будет в любом случае. Прав Эрик Мейер: гриды — это целая новая эра верстки. Так что спешите прикоснуться к чудесному будущему! Пока оно не стало скучным настоящим:)

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

8 Комментарии
  1. Антон

    Ну какие нафиг гриды для правильной сетки? Еще много заказчиков хочет IE9 поддерживать.

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

      Для IE9 есть старое решение, которое по-прежнему лучше чем ничего и вполне может быть фолбэком для правильного.

      1. Антон

        Ну так зачем в таком случае писать в 2 раза больше кода? Это старое решение будет работать и на новых браузерах.

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

          Старое решение всё-таки иногда подглючивает:(. Новое железобетоннее. И дописать надо всего-то десяток строчек (5 строк на само решение, 3 на отмену псевдоэлемента и две на @supports вместе со скобками) — по-моему, не такая уж большая цена за надежность сетки в новых браузерах и дополнительные возможности распределения места. А инлайн-блоки превратятся в грид-элементы автоматически, точно так же, как во флекс-контейнере они превращаются во флекс-элементы.

  2. Алексей

    Ну в Хроме (который, по-идее, должен обновляться автоматически) у меня не сработало:
    http://my.jetscreenshot.com/demo/20170409-fuub-22kb.jpg (по элементу на строке)
    В Лисе всё ок.
    А вообще, намёк понятен — пора :)
    С чего же лучше начать изучение гридов, и чем продолжить?..

  3. Алексей

    Ну, в общем, понял примерно следующее:
    Вот этими волшебными строчками:
    grid-template-columns: repeat(auto-fit, var(—item-width));
    В принципе решается вся задача.
    При этом, как заполняются элементы, определяется свойством justify-content.
    А последний ряд можно заполнить двумя способами, так, чтоб его элементы попадали в те ряды, которые были сформированы вышележащими столбцами, или так, чтоб столбцы образовывались в соответствии с количеством элементов в последнем ряду.
    Для первого случая в аргументах reapet используется ключевое слово auto-fill, а для второго —
    auto-fit, который коллапсирует пустые элементы.
    Похоже на правду или что-то существенное упустил?…
    ЗЫ: ну и понятно, что в проекте лучше делать так:
    ..старый способ..
    supports grid (не помню точный синтаксис)
    если да, то
    убираем нафик псевдоэлемент назначаем новую раскладку.

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

      Всё так! Только последний ряд, если он не единственный, всегда повторяет ритм предыдущих рядов (и в этом весь смысл сетки). А вот если он единственный — тут да, возможны варианты:)

      1. Алексей

        А, понял. Спасибо!

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

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

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

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