От автора: недавно я работал над современной версией блогролла. Идея заключалась в том, чтобы предложить читателям подборку последних постов из этих блогов в макете в стиле журнала, а не просто выложить список любимых постов в боковой панели.
Легкой частью было получение списка постов с выдержками из избранных RSS-каналов. Для этого мы использовали плагин WordPress, Feedzy lite, который может объединять несколько каналов в один упорядоченный по времени список — идеально подходит для демонстрации последних обновлений. Сложнее всего было заставить все это выглядеть потрясающе.
Пользовательский интерфейс списка плагина по умолчанию довольно невыразительный, поэтому я хотел, чтобы он выглядел как веб-сайт газеты или журнала со смесью маленьких и больших панелей «рекомендуемого контента».
Это похоже на идеальный случай для CSS Grid! Создать макет сетки для разных макетов, скажем, один макет из пяти столбцов и один макет из трех, а затем использовать медиа-запросы для переключения между ними в разных контрольных точках. Правильно? Но нужны ли нам эти медиа-запросы — и все хлопоты, связанные с определением контрольных точек — когда мы можем использовать параметры сетки auto-fit для автоматического создания гибкой адаптивной сетки?
Подход выглядел заманчиво, но когда я начал вводить интервалы, охватывающие несколько колонок, я столкнулся с проблемой переполнения сетки на узких экранах. Медиа-запросы оказались единственным решением. То есть пока я не нашел обходной путь!
Посмотрев несколько руководств по CSS Grid, я обнаружил, что они в основном делятся на два лагеря:
Руководства, которые показывают, как создать интересный макет с составными элементами, но для фиксированного числа столбцов.
Руководства, которые объясняют, как сделать адаптивную сетку, которая автоматически изменяет размеры, но со всеми элементами сетки одинаковой ширины (то есть без каких-либо составных столбцов).
Я хочу, чтобы сетка делала и то, и другое: создавала полностью адаптивный гибкий макет, который также включает в себя адаптивное изменение размеров многоколоночных элементов.
Прелесть в том, что как только вы поймете ограничения адаптивных сеток, и почему и когда интервалы из нескольких колонок нарушают адаптивность сетки, можно определить адаптивный макет журнала с помощью всего дюжины строк кода плюс один простой медиа-запрос (или даже без медиа-запросов, если вы хотите ограничить параметры диапазона).
Вот визуальное представление плагина RSS прямо из коробки, и того, как он будет выглядеть после стилизации.
(Демо)
Этот журнальный макет сетки полностью реагирует на изменение количества колонок. На странице отображается около 50 сообщений, но код макета не зависит от количества отображаемых элементов. Увеличьте число элементов до ста, и макет остается интересным до самого конца.
Все это достигается с помощью только CSS и всего одного медиа-запроса, чтобы иметь дело с отображением в один столбец на самом узком из экранов (т.е. меньше 460 пикселей).
Невероятно, но этот макет может определяться всего 21 строкой CSS (не включая глобальные стили контента). Однако, чтобы достичь такой гибкости в нескольких строках кода, мне пришлось углубиться в более сложные аспекты некоторых сеток CSS и научиться обходить некоторые из внутренних ограничений.
Основные элементы кода, которые создают этот макет, невероятно короткие, что является свидетельством великолепия CSS Grid:
.archive { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); grid-gap: 32px; grid-auto-flow: dense; } /* Extra-wide grid-posts */ .article:nth-child(31n + 1) { grid-column: 1 / -1; } .article:nth-child(16n + 2) { grid-column: -3 / -1; } .article:nth-child(16n + 10) { grid-column: 1 / -2; } /* Single column display for phones */ @media (max-width: 459px) { .archive { display: flex; flex-direction: column; } }
Методы, описанные в этой статье, можно одинаково хорошо использовать для стилизации любого динамически генерируемого контента, такого как вывод из виджета последних записей, архивные страницы или результаты поиска.
Создание адаптивной сетки
Я настроил семнадцать элементов, отображающих разнообразный макет контента — заголовки, изображения и выдержки — которые все содержатся в оболочке:
<div class="archive"> <article class="article"> <!-- content --> </article> <!-- 16 more articles --> </div>
Код, который превращает эти элементы в адаптивную сетку, удивительно компактен:
.archive { /* Define the element as a grid container */ display: grid; /* Auto-fit as many items on a row as possible without going under 180px */ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); /* A little spacing between articles */ grid-gap: 1em; }
Обратите внимание, что высота строк автоматически регулируется для размещения самого высокого содержимого в строке. Если вы измените ширину демо-блока, вы увидите, что элементы плавно растягиваются и сжимаются, а количество столбцов изменяется от одного до пяти соответственно.
Здесь магия CSS Grid — это ключевое слово auto-fit, которое используется вместе с функцией minmax(), которая применяется к grid-template-columns.
Как это работает
Мы могли бы достичь только пятиколоночного макета, используя следующее:
.archive { display: grid; grid-template-columns: repeat(5, 1fr); }
Однако это приведет к созданию пяти колонок, которые растягиваются и сжимаются на экранах разной ширины, но всегда остается пять колонок, в результате чего они становятся очень узкими на маленьких экранах. Первой мыслью может быть создание набора медиа-запросов и переопределение сетки с различным количеством колонок. Это будет работать нормально, но с помощью ключевого слова auto-fit, все это делается автоматически.
Для автоматической подгонки, чтобы это работало так, как мы хотим, нам нужно использовать функцию minmax(). Она указывает браузеру, до какой максимальной и минимальной ширины колонки могут быть сжаты или растянуты. Если размер меньше, количество колонок автоматически уменьшится. Если больше, количество колонок увеличится.
.archive { grid-template-columns: repeat (auto-fit, minmax(180px, 1fr)); }
В этом примере браузер разместит столько колонок, сколько доступно при ширине 180 пикселей. Если для колонок останется место, все они будут растягиваться одинаково, разделяя оставшееся пространство между собой — вот что говорит значение 1fr: отвести колонкам по одной равной части доступной ширины.
Растяните окно, и по мере увеличения доступного пространства столбцы будут растягиваться в равной степени, чтобы использовать дополнительное пространство. Столбцы будут растягиваться, пока доступное пространство не позволит добавить дополнительный столбец шириной в 180 пикселей, после чего появится новый столбец. Уменьшите ширину экрана, и сетка будет настроена соответственно – вплоть до одной колонки в ширину. Магия!
И мы получаем всю эту адаптивность только из одной строки кода. Разве это не круто?
Создание диапазонов на несколько колонок с помощью функции «autoflow: dense»
На данный момент у нас есть адаптивная сетка, но все элементы имеют одинаковую ширину. Для макета журнала нам нужен контент, охватывающий две или более колонок, или даже, возможно, все колонки.
Чтобы создать многоколоночные диапазоны, мы можем добавить к элементам сетки функцию column-span. Например, если мы хотим, чтобы третий элемент в нашем списке имел ширину в две колонки, мы можем добавить:
.article:nth-child(3) { grid-column: span 2; }
Однако, когда мы начнем добавлять диапазоны, может возникнуть ряд проблем. Во-первых, в сетке могут появляться пробелы, потому что широкий элемент может не помещаться в ряде, поэтому auto-fit вытолкнет его на следующий ряд, оставляя пробел там, где он был:
Простое решение — добавить к элементу сетки grid-auto-flow: dense, что указывает браузеру заполнить пробелы другими элементами, эффективно создавая более узкий контент вокруг более широких элементов, например:
Обратите внимание, что порядок элементов теперь нарушен — четвертый элемент перед третьим, который в два раза шире. Насколько я могу судить, сделать с этим ничего нельзя, и это одно из ограничений CSS Grid, которые вы должны принять.
Способы указания диапазонов
Есть несколько способов указать, сколько колонок элемент должен охватывать. Самый простой — применить grid-columns: span [n] для одного из элементов, где n указывает количество колонок, которые элемент будет охватывать. Третий элемент в нашем макете имеет grid-column: span 2, что объясняет, почему он в два раза шире других элементов, которые охватывают только одну колонку.
Другие методы требуют явного определения линий сетки. Система нумерации линий сетки выглядит следующим образом:
Линии сетки могут быть указаны слева направо, используя положительные значения (например, 1, 2, 3), или справа налево, используя отрицательные значения (например, -1, -2, -3). Их можно использовать для размещения элементов в сетке с помощью свойства grid-column, например:
.grid-item { grid-column: (start track) / (end track); }
Таким образом, это дает нам дополнительные способы указать составной элемент. Это особенно гибкое решение, поскольку начальное или конечное значение можно заменить ключевым словом span. Например, синий блок с тремя колонками в приведенном выше примере можно создать, добавив любое из следующего к восьмому элементу сетки:
grid-column: 3 / 6
grid-column: -4 / -1
grid-column: 3 / span 3
grid-column: -4 / span 3
grid-column: span 3 / -1
и т.п.
В неадаптивной сетке (то есть с фиксированными столбцами) все они дают одинаковый эффект (например, синий блок выше), однако, если сетка является адаптивной, и количество колонок изменяется, начинают проявляться их различия. Определенные диапазоны столбцов нарушают макет при автоматическом расширении сетки, что делает эти два метода несовместимыми. К счастью, есть некоторые решения, которые позволяют нам успешно сочетать их.
Однако сначала нам нужно понять проблему.
Проблема переполнения с боковой прокруткой
Вот некоторые выделенные области, созданные с использованием описанных выше техник:
(Демо)
Все выглядит хорошо при полной ширине (пять колонок), но при изменении размера до двух колонок макет разбивается следующим образом:
Как вы можете видеть, наша сетка утратила адапитвность и, хотя контейнер сжался, сетка пытается сохранить все пять столбцов. Чтобы сделать это, уже не сохраняется равная ширина колонок и сетка выходит за пределы контейнера справа, что приводит к появлению горизонтальной прокрутки.
Почему это происходит? Проблема возникает из-за того, что браузер пытается соблюдать явные линии сетки, которые мы указали. При такой ширине auto-fit сетка должна неявно отображать две колонки, но наша система нумерации линий сетки противоречит этому, явно ссылаясь на пятую линию сетки. Это противоречие приводит к беспорядку. Чтобы правильно отобразить нашу неявную сетку из двух колонок, допустимы только, номера строк 1, 2, 3 и -3, -2, -1:
Но если какой-либо из элементов сетки содержит ссылку на grid-column, которые расположены вне этого, например, линии сетки с номерами 4, 5 или 6 (или -4, -5, -6), браузер получает противоречивые указания. С одной стороны, мы попросили его автоматически создавать гибкие колонки (что должно давать нам две колонки при такой ширине экрана), но мы также явно сослались на линии сетки, которые не отображаются в сетке из двух колонок. Когда существует конфликт между неявными (автоматическими) колонками и явным числом колонок, grid всегда выбирает явную сетку; отсюда нежелательные колонки и горизонтальное переполнение (которое также было названо потерей CSS-данных). Как и при использовании номеров линий сетки, диапазоны также могут создавать явные колонки. Так, grid-column: span 3 (восьмой элемент сетки в демонстрации) заставляет сетку явно принимать, по крайней мере, три колонки, в то время как мы хотим, чтобы неявно отображались две.
На данный момент может показаться, что единственный способ продвинуться вперед — это использовать медиа-запросы, чтобы изменить значения grid-column при ширине, на которой наш макет разрывается — но не спешите! Это то, что я предполагал с самого сначала. Но, подумав об этом немного больше и поэкспериментировав с различными вариантами, я обнаружил, что существует ограниченный набор обходных путей, которые работают вплоть до двух колонок, и для них нужен только один медиа-запрос, чтобы охватить макет из одной колонки для самых узких экранов.
Решения
Я понял, что хитрость заключается в том, чтобы задавать диапазоны только с использованием линий сетки, которые отображаются в самой узкой сетке. В данном случае это сетка из двух колонок. (Мы будем использовать медиа-запрос, чтобы охватить сценарий с одной колонкой для очень узких экранов.) Это означает, что мы можем безопасно использовать линии сетки 1, 2 и 3 (или -3, -2 и -1), не нарушая сетку.
Сначала я думал, что это означает, что я должен ограничить себя максимальным диапазоном в две колонки, используя следующие комбинации:
grid column: span 2
grid-column: 1 /3
grid-column: -3 / -1
Который остается полностью адаптивным вплоть до двух колонок:
Хотя данное решение работает, это довольно ограничено с точки зрения дизайна, и не особенно захватывающе. Я хотел иметь возможность создавать диапазоны шириной в три, четыре или даже пять колонок на больших экранах. Но как? Моей первой мыслью было, что мне придется прибегать к медиа-запросам (OMG, старые привычки тяжело умирают!), но я пытался отойти от этого подхода и по-другому взглянуть на адаптивный дизайн.
Еще раз посмотрев на то, что мы можем сделать только с линиями с 1 по 3 и с -3 до -1, я постепенно осознал, что могу смешивать положительные и отрицательные номера линий для начальных и конечных значений колонок сетки, например, 1/-3 и 2/-2. На первый взгляд это не кажется очень интересным. Все изменится, когда вы поймете, где расположены эти линии при изменении размера сетки: эти составные элементы меняют ширину в зависимости от размера экрана. Это открыло совершенно новый набор возможностей для адаптивных диапазонов: элементы, которые будут охватывать различное количество колонок по мере того, как экран становится шире, без необходимости использования медиа-запросов.
Первый пример, который я исследовал, это grid-column: 1/-1. Это заставляет элемент работать как баннер на всю ширину, охватывающий все колонки от первой до последней при всех номерах столбцов. Это даже работает при ширине в одну колонку!
Используя grid-column: 1/-2, можно создать смещенный к левому краю диапазон почти на всю ширину, который будет всегда оставлять одну колонку справа. При сокращении общей ширины сетки до двух колонок, диапазон будет уменьшаться до одной колонки. Удивительно, но это даже работает, когда общая ширина уменьшается до одной колонки. (Причина, по-видимому, заключается в том, что сетка не свернет элемент до нулевой ширины, поэтому он остается с шириной в одну колонку, как и при grid-column: 1/1.) Я предположил, что grid-column: 2/-1 будет работать аналогично, но диапазон будет смещаться к правому краю, и по большей части это так, за исключением ширины сетки в одну колонку, когда это вызывает переполнение.
Затем я попробовал значение 1/-3, которое отлично работало на более широком экране, отображая как минимум три колонки, и на меньших экранах, отображалась одна колонка. Я думал, что это будет работать странно в сетке из двух колонок, поскольку первая линия сетки совпадает с линией сетки -3. К моему удивлению, диапазон по-прежнему отображается нормально, как элемент из одной колонки.
После долгих экспериментов я придумал одиннадцать возможных значений колонок сетки, используя номера линий из сетки с двумя столбцами. Удивительно, но три из них работают вплоть до одноколоночных макетов. Еще семь работают при двух колонках, а для отображения одной колонки потребуется всего один медиа-запрос.
Вот полный список:
Адаптивные значения колонок сетки, показывающие, как они отображаются при разных размерах экрана в auto-fit сетке. (Демо)
Как вы можете видеть, хотя это ограниченное подмножество для каждого возможного адаптивного интервала, на самом деле существует множество возможностей.
2/-2 — это интересно тем, что создает центрированный интервал, который работает вплоть до одного столбца!
3/-1 наименее полезен, поскольку вызывает переполнение даже с двумя колонками.
3/-3 оказался сюрпризом.
Используя различные варианты значений grid-column из этого списка, можно создать интересный и полностью адаптивный макет. Используя один медиа-запрос для самого узкого одноколоночного макета, у нас есть десять различных шаблонов интервала grid-column, с которыми можно работать.
Медиа-запрос для одной колонки, как правило, также прост. В этой последней демонстрации мы возвращаемся к использованию flexbox на экранах меньшего размера:
@media (max-width: 680px) { .archive { display: flex; flex-direction: column; } .article { margin-bottom: 2em; } }
Вот последняя таблица, которая, как вы можете видеть, полностью адаптивна от одного до пяти колонок:
(Демо)
Использование: nth-child() для повтора отображения переменной длины
Последним трюком, который я использовал, чтобы сократить свой код до двух десятков строк, был селектор :nth-child(n), который я использовал для стилизации нескольких элементов в сетке. Я хотел, чтобы стили интервала применялись к нескольким элементам в моей ленте, чтобы избранные записи регулярно отображались на странице. Для начала я использовал список селекторов, разделенных запятыми, например:
.article:nth-child(2), .article:nth-child(18), .article:nth-child(34), .article:nth-child(50) { background-color: rgba(128,0,64,0.8); grid-column: -3 / -1; }
Но вскоре я нашел это громоздким, особенно потому, что мне пришлось повторять этот список для каждого дочернего элемента, который я хотел стилизовать в каждой статье — например, заголовок, ссылки и так далее. Во время создания прототипа, если я хотел поэкспериментировать с позицией составных элементов, мне пришлось вручную менять номера в каждом из этих списков, что было утомительно и подвержено ошибкам.
Именно тогда я понял, что мог бы использовать мощный псевдо-селектор функций :nth-child, вместо простого целого числа, как я использовал в списке выше. :nth-child(n) также может принимать уравнение, например :nth-child(2n+ 2), которое будет предназначаться для каждого второго дочернего элемента.
Вот как я использовал :nth-child([formula]) для создания синих панелей полной ширины, которые отображаются в самом верху страницы и повторяются чуть более, чем на половине страницы:
.article:nth-child(31n + 1) { grid-column: 1 / -1; background: rgba(11, 111, 222, 0.5); }
Часть в скобках (31n + 1) обеспечивает выбор 1-го, 32-го, 63-го и т. д. дочернего элемента. Браузер запускает цикл, начинающийся с n = 0 (в этом случае 31 * 0 + 1 = 1), затем n=1( 31 * 1 + 1 = 32), затем n=2( 31 * 2 + 1 = 63). В последнем случае, браузер понимает, что 63-его потомка нет, и после этого он останавливает цикл, применяя CSS к 1-ому и 32-ому потомкам.
Я делаю что-то похожее для фиолетовых блоков, которые чередуются на странице справа налево:
.article:nth-child(16n + 2) { grid-column: -3 / -1; background: rgba(128, 0, 64, 0.8); } .article:nth-child(16n + 10) { grid-column: 1 / -2; background: rgba(128, 0, 64, 0.8); }
Первый селектор для фиолетовых прямоугольников справа. 16n + 2 обеспечивает, чтобы стиль применялся к каждому 16-ому элементу сетки, начиная со второго.
Второй селектор выбирает правые блоки. Он использует тот же диапазон (16n), но с другим смещением (10). В результате эти блоки циклически отображаются справа для элементов сетки 10, 26, 42 и т. д.
Когда дело доходит до визуального оформления элементов сетки и их содержимого, я использовал другой прием, чтобы уменьшить количество повторений. Для стилей, которые используются в обоих блоках (например, background-color), можно использовать один селектор:
.article:nth-child(8n + 2) { background: rgba(128, 0, 64, 0.8); /* Other shared syling */ }
Это будет предназначено для элементов 2, 10, 18, 26, 34, 42, 50 и так далее. Другими словами, он выбирает как левые, так и правые блоки.
Это работает, потому что 8n — это ровно половина 16n, а смещения, используемые в двух отдельных селекторах, имеют разницу 8 (т.е. разница между +10 и +2 равна 8).
Заключение
Прямо сейчас CSS Grid можно использовать для создания гибких адаптивных сеток с минимальным кодом, но это имеет некоторые существенные ограничения при позиционировании элементов без использования медиа-запросов.
Было бы здорово иметь возможность задавать интервалы колонок, которые не приводили бы к выходу за пределы окна на меньших экранах. На данный момент мы эффективно указываем браузеру: «Создай адаптивную сетку, пожалуйста», и он прекрасно это делает. Но когда мы продолжаем говорить: «О, и сделайте этот элемент сетки размером в четыре колонки», он шипит что-то в ответ об узких экранах, и отдает приоритет запросу с четырьмя столбцами, а не адаптивной сетке. Было бы здорово иметь возможность указать сетке отдавать предпочтение адаптивности, а не диапазону. Что-то вроде этого:
.article { grid-column: span 3, autofit; }
Еще одна проблема с адаптивными сетками — последний ряд. При изменении ширины экрана последний ряд часто не заполняется. Я потратил много времени на поиски способа сделать последний элемент сетки диапазоном из оставшихся колонок (и, следовательно, заполненным), но, похоже, вы не можете сделать это в Grid прямо сейчас. Было бы неплохо, если бы мы могли указать начальную позицию элемента с помощью ключевого слова, такого как auto, означающего: «Пожалуйста, растягивай левый край везде, где он смещается». Как, например:
.article { grid-column: auto, -1; }
… чтобы левый край растягивался до конца ряда.
Автор: Keir Watson
Источник: https://css-tricks.com
Редакция: Команда webformyself.