Связь Grid позиционирования с другими методами позиционирования

Перевод не завершен. Пожалуйста, помогите перевести эту статью с английского.

CSS Grid Layout спроектирован таким образом, чтобы работать вместе с другими частями CSS и составлять с ними законченную систему создания макетов страниц. В рамках этого руководства, мы объясним, каким образом сочетать гриды с другими техниками, которыми Вы, возможно, уже пользуетесь в своей работе. 

Grid и flexbox

Основное различие между CSS Grid Layout и CSS Flexbox Layout  в том, что flexbox предназначен для позиционирования элементов в одном направлении, то есть, либо в строке, либо в колонке. Grid же был разработан для позиционирования элементов в двумерной системе, то есть, для одновременного позиционирования и в строке, и в колонке. Однако, в двух спецификациях есть некоторые общие черты, и если вы уже научились укрощать flexbox, вы увидите сходства, которые помогут вам разобраться и с Grid.

Одномерное vs Двумерное позиционирование

Простой пример поможет нам продемонстрировать разницу между одно- и двумерным позиционированием.

В пером примере мы воспользуемся flexbox для того, чтобы разместить несколько блоков. Предположим, что у нас есть пять дочерних элементов в контейнере, зададим им значения flex-свойств таким образом, чтобы их размер увеличивался и уменьшался, начиная с базового в 200px.

Также установим свойство flex-wrap в значение wrap . Это приведет к тому, что если свободного пространства в нашем контейнере будет не хватать для размещения элемента в 200px, наши элементы спокойно перейдут на новую строку. 

<div class="wrapper">
   <div>One</div>
   <div>Two</div>
   <div>Three</div>
   <div>Four</div>
   <div>Five</div>
</div>
.wrapper {
   display: flex;
   flex-wrap: wrap;
}
.wrapper > div {
   flex: 1 1 200px;
}

На картинке вы видите, что два элемента перешли на новую строку. Эти элементы поделили свободное пространство между собой, а не выравнялись по элементам над ними. Происходит это потому, что каждая новая строка (или колонка, если мы работаем с колонками) становится новым flex-контейнером. А во flex-контейнере распределение свободного пространства действует в рамках всей строки. 

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

Те же яйца, вид в профиль: тот же макет, но с CSS гридами

В примере ниже мы создаем тот же самый макет, но используя гриды. На этот раз у нас три трека-колонки шириной в 1fr . И при этом нам не требуется задавать какие-либо свойства дочерним элементам, потому что они самостоятельно занимают по одной ячейке созданного грида. Как Вы видите, наши элементы лежат в жесткой сетке и выравниваются и по строке, и по колонке. Поскольку у нас пять элементов, в результате мы получаем пустую ячейку в конце второй строки. 

<div class="wrapper">
   <div>One</div>
   <div>Two</div>
   <div>Three</div>
   <div>Four</div>
   <div>Five</div>
</div>
.wrapper {
   display: grid;
   grid-template-columns: repeat(3, 1fr);
}

Если Вы колеблетесь, что выбрать - flexbox или грид, задайте себе простой вопрос:

  • мне нужно управлять размещением элементов в строке или в колонке - окей, нужен flexbox
  • мне нужно управлять размещением элементов и в строке, и в колонке – окей, нужен грид

Что важнее: контент или макет?

В дополнение к различию между позиционированию в одном направлении и позиционированию в двух направлениях, существует еще один способ решить, нужен ли Вам макет, основанный на flexbox или макет, основанный на гридах. Flexbox работает исходя из размеров контента. Идеальный случай использования flexbox - когда у Вас есть набор элементов, а Вам нужно распределить их в контейнере равномерно. Вы позволяете размеру содержимого элементов решить, сколько пространства должен забрать каждый элемент. Если элементы переходят на новую строку, они забирают для себя пространство, исходя из своих размеров и того свободного места, которое есть в этой строке.

Грид работает, исходя из макета. Когда Вы используете CSS Grid Layout, Вы создаете структуру и затем размещаете элементы именно в этой структуре или же позволяете правилам авто-размещения разместить элементы в грид-ячейках в соответствии с жестко заданной сеткой. Конечно, существует возможность создавать треки, подстраивающиеся под размер контента, но при этом они также меняют саму структуру.

Поэтому, если Вы используете flexbox и вдруг обнаруживаете, что ограничиваете эластичность элементов, возможно, Вам нужно посмотреть в сторону CSS Grid Layout. Например, в том случае, если Вы процентами подгоняете ширину flex-элемента, чтобы выровнять его по элементам в строке сверху. В такой ситуации гриды кажутся более оптимальным выбором. 

Выравнивание блоков

Самой волнующей функциональностью flexbox для многих из нас была возможность впервые управлять выравниванием блоков. С помощью flexbox можно легко отцентрировать блок на странице. Флекс-элементы способны растягиваться на всю длину контейнера - значит, колонки равной высоты из мечты стали реальностью. Существовал целый ряд вещей, которые нам хотелось сделать очень давно, и для воплощения которых приходилось изобретать различные хаки.

Свойства выравнивания из спецификации flexbox были добавлены в новую спецификацию, названную Box Alignment Level 3. А это означает, что они могут использоваться и в других спецификациях, в том числе и в Grid Layout. 

Дальше в нашем руководстве мы подробно рассмотрим выравнивание блоков Box Alignment и то, как оно работает в Grid Layout, а здесь давайте рассмотрим два простых примера, и сравним flexbox и гриды.

В первом примере, использующем flexbox, у нас есть контейнер с тремя элементами. Для блока-обертки wrapper установлено свойство min-height, и оно задает высоту flex-контейнера. Мы установили свойство align-items flex-контейнера в значение flex-end , поэтому элементы выравниваются по концу flex-контейнера. Мы также установили значение свойства align-self для box1  таким образом, что оно перезапишет поведение по умолчанию и заставит наш блок растянутся на всю высоту контейнера. Для box2 свойство align-self установлено таким образом, что блок перепрыгнет в начало flex-контейнера.

<div class="wrapper">
   <div class="box1">One</div>
   <div class="box2">Two</div>
   <div class="box3">Three</div>
</div>
.wrapper {
   display: flex;
   align-items: flex-end;
   min-height: 200px; 
}
.box1 {
   align-self: stretch;
}
.box2 {
   align-self: flex-start;
}

Тем временем в параллельной вселенной: выравнивание в CSS Гридах

Второй пример использует грид, чтобы создать тот же самый макет, и на этот раз мы рассмотрим то, как свойства выравнивания блоков применяются к гридам. Вместо flex-start и flex-end мы задаем start и end . В случае с макетом на гридах мы выравниваем элементы внутри их грид-обалсти, в данном примере - это одна единственная грид-ячейка, но в целом грид-область может состоять из нескольких грид-ячеек.

<div class="wrapper">
   <div class="box1">One</div>
   <div class="box2">Two</div>
   <div class="box3">Three</div>
</div>
.wrapper {
   display: grid;
   grid-template-columns: repeat(3,1fr);
   align-items: end;
   grid-auto-rows: 200px;
}.box1 {
   align-self: stretch;
}
.box2 {
   align-self: start;
}

Единица fr и flex-basis

Мы уже видели, как работает единица fr в случае пропорционального распределения доступного пространства между грид-треками в грид-контейнере.  При комбинировании fr с функцией minmax() мы получаем поведение, очень похожее на свойство flex в flexbox - и при этом по-прежнему можем создавать макет в двумерной системе.

Если вернуться к примеру, демонстрирующему различия между одно-и двумерным позиционированиями, можно увидеть, что существует также и различие в самом способе того, как две техники работают с отзывчивыми макетами.  С макетом на flex, если мы уменьшаем или увеличиваем размер окна, flexbox аккуратно перераспределяет количество элементов в каждой строке в соответствии с доступным пространством. Так, если у нас достаточно места, чтобы разместить все пять наших элементов в одной строке, они и будут размещены в одной строке. Если же контейнер узкий, то в строке у нас будет место только для одного элемента.

В сравнении грид-версия всегда содержит три трека-колонки. Эти треки-колонки будут расширяться и сужаться, но их всегда будет три, раз мы запросили три при задании грида. 

Автозаполнение грид-треков

Можно создать эффект, похожий на поведение flexbox, и при этом по-прежнему держать контент в жесткой сетке из строк и колонок, если задать структуру треков, используя repeat-нотацию и свойства auto-fill и auto-fit.

В примере ниже мы используем ключевое слово auto-fill вместо целого числа в repeat-нотации и задаем структуру треков размером в 200 пикселей. Это значит, что грид создаст столько треков-колонок размером в 200 пикселей, сколько их может разместиться в контейнере.

<div class="wrapper">
   <div>One</div>
   <div>Two</div>
   <div>Three</div>
</div>
.wrapper {
   display: grid;
   grid-template-columns: repeat(auto-fill, 200px);
}

Переменное количество треков

Давайте вспомним пример с flexbox, когда элементы, размер которых больше 200 пикселей, переходят на новую строку. Тот же самый эффект в гридах мы можем получить комбинируя auto-fill и функцию minmax(). В примере ниже мы создаем автозаполненные треки с помощью minmax. Мы хотим, чтобы треки были как минимум 200 пикселей в ширину, это наше минимальное значение, а для максимального зададим 1fr. В процессе, когда браузер вычисляет, сколько блоков в 200 пикселей может разместиться в контейнере - при этом учитывая грид-зазоры - он расценивает максимум 1fr как инструкцию распределить оставшееся свободное пространство между этими блоками.

<div class="wrapper">
   <div>One</div>
   <div>Two</div>
   <div>Three</div>
</div>
.wrapper {
   display: grid;
   grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}

Собственно, теперь у нас есть возможность создавать гриды с переменным количеством или с переменным размером треков и при этом по-прежнему держать элементы в жесткой сетке из строк и колонок.

Гриды и абсолютно позиционированные элементы

Грид взаимодействует с абсолютно позиционированными элементами, что отнюдь не бесполезно, если Вы хотите разместить элемент внутри грида или грид-области. В спецификации описано поведение грида и тогда, когда грид-контейнер является контейнерным блоком (containing block) и тогда, когда грид-контейнер - родительский элемент для абсолютно позиционированного элемента.

Грид-контейнер как контейнерный блок

Для того, чтобы превратить грид-контейнер в контейнерный блок Вам нужно добавить ему свойство position со значением relative. Если после этого задать какому-нибудь грид-элементу position: absolute , грид-контейнер станет контейнерным блоком для данного элемента.

В примере ниже у нас есть блок-обертка с четырьмя дочерними элементами. Третий элемент абсолютно позиционирован и одновременно размещен в гриде с помощью привязки к грид-линиям. У грид-контейнера position: relative , поэтому он становится контекстом позиционирования для нашего третьего элемента.

<div class="wrapper">
   <div class="box1">One</div>
   <div class="box2">Two</div>
   <div class="box3">
    Этот блок абсолютно позиционирован. 
В нашем примере грид-контейнер является контейнерным блоком, поэтому значения сдвига абсолютного позиционирования отсчитываются от внешнего края той области, в которой размещен элемент.
   </div>
   <div class="box4">Four</div>
</div>
.wrapper {
   display: grid;
   grid-template-columns: repeat(4,1fr);
   grid-auto-rows: 200px;
   grid-gap: 20px;
   position: relative;
}
.box3 {
   grid-column-start: 2;
   grid-column-end: 4;
   grid-row-start: 1;
   grid-row-end: 3;
   position: absolute;
   top: 40px;
   left: 40px;
}

Вы видите, что наш элемент занимает область от колоночной грид-линии 2 до колоночной грид-линии 4 и начинается после строчной линии 1. С помощью свойств left и top мы сдвигаем его относительно этой области. В то же время, он изымается из потока так же, как и любой другой элемент с абсолютным позиционированием, поэтому правила авторазмещения теперь помещают другие элементы на его место. Абсолютное позиционирование нашего элемент также не приводит к появлению новой строки.

Попробуйте удалитьposition: absolute из правил для .box3 , и увидете, как он размещался бы без абсолютного позиционирования.

Грид-контейнер в качестве родительского элемента

Если у абсолютно позиционированного элемента в качестве родительского контейнера выступает грид, не создающий новый контекст позиционирования, наш элемент также вытаскивается из потока, как и в предыдущем примере. Но в этом случае контекстом позиционирования будет любой элемент, который как раз и создает этот контекст позиционирования. Словом, если в нашем примере мы уберем position: relative из блока-обертки, контекстом позиционирования станет область просмотра, что хорошо видно на рисунке ниже.

Image of grid container as parent

Еще раз: наш элемент больше не занимает пространство в грид-макете и не влияет на то, как располагаются другие элементы при авторазмещении.

А что если родительский элемент - это грид-область?

Если абсолютно позиционированный элемент находится в грид-области, Вы можете создать контекст позиционирования из этой области. В примере ниже у нас тот же грид-макет, что и раньше, но теперь мы разместили элемент внутри .box3 .

Задаем .box3 свойство position в значении relative и затем перемещаем наш под-элемент с помощью свойств сдвига.  В данном случае контекстом позиционирования является грид-область.

<div class="wrapper">
   <div class="box1">One</div>
   <div class="box2">Two</div>
   <div class="box3">Three
       <div class="abspos">
        Этот блок абсолютно позиционирован. В данном примере контекстом позиционирования являетсягрид-область, поэтому значения сдвига отсчитываются от внешних краев грид-области.
       </div>
   </div>
   <div class="box4">Four</div>
</div>
.wrapper {
   display: grid;
   grid-template-columns: repeat(4,1fr);
   grid-auto-rows: 200px;
   grid-gap: 20px;
}
.box3 {
   grid-column-start: 2;
   grid-column-end: 4;
   grid-row-start: 1;
   grid-row-end: 3;
   position: relative;
}
.abspos {
   position: absolute;
   top: 40px;
   left: 40px;
   background-color: rgba(255,255,255,.5);
   border: 1px solid rgba(0,0,0,0.5);
   color: #000;
   padding: 10px;
}

Грид и display: contents

Последнее, о чем нужно упомянуть, говоря о взаимодействии гридов с другими спецификациями, касающимися позиционирования элементов, - это взаимодействие между CSS Grid Layout и display: contents. Значение contents свойства display - новое свойство CSS, которое описывается в спецификации Display следующим образом:

“Сам элемент не генерирует никаких блоков (боксов), но его дочерние элементы и его псевдо-элементы по-прежнему генерируют блоки, в установленном порядке. Относительно генерации и позиционирования блоков элемент должен восприниматься так, как если бы он полностью замещался своими дочерними элементами и псевдо-элементами в дереве документа.”

Если Вы пишете для элемента  display: contents , блок (бокс), который он должен создать в дереве документа исчезает, а вот блоки его дочерних элементов и его псевдо-элементов переходят на один уровень вверх. А значит это то, что дочерние элементы грид-элемента могут сами стать грид-элементами. Звучит непонятно? Давайте разберемся на простом примере. В разметке ниже у нас есть грид. Первый элемент этого грида настроен так, чтобы занимать все три трека-колонки. У него есть три вложенных элемента. Поскольку эти вложенные элементы не являются прямыми потомками грида, они не становятся частью грид-макета и отображаются, как обычные блоки.

<div class="wrapper">
   <div class="box box1">
       <div class="nested">a</div>
       <div class="nested">b</div>
       <div class="nested">c</div>
   </div>
   <div class="box box2">Two</div>
   <div class="box box3">Three</div>
   <div class="box box4">Four</div>
   <div class="box box5">Five</div>
</div>
.wrapper {
   display: grid;
   grid-template-columns: repeat(3, 1fr);
   grid-auto-rows: minmax(100px, auto);
}
.box1 {
   grid-column-start: 1;
   grid-column-end: 4;
}

Если мы теперь добавим правило display: contents для  box1, блок этого бокса исчезнет, зато дочерние элементы станут грид-элементами и будут расположены в соответствии с правилами авторазмещения.

<div class="wrapper">
   <div class="box box1">
       <div class="nested">a</div>
       <div class="nested">b</div>
       <div class="nested">c</div>
   </div>
   <div class="box box2">Two</div>
   <div class="box box3">Three</div>
   <div class="box box4">Four</div>
   <div class="box box5">Five</div>
</div>
.wrapper {
   display: grid;
   grid-template-columns: repeat(3, 1fr);
   grid-auto-rows: minmax(100px, auto);
}
.box1 {
   grid-column-start: 1;
   grid-column-end: 4;
   display: contents;
}

Таким образом мы можем заставить вложенные элементы вести себя, словно они часть грида (и в некотором смысле имитация того поведения, которое должны будут реализовать подгриды (subgrids), когда руки разработчиков браузеров до них доберутся). Точно так же можно использовать display: contents с flexbox, чтобы вложенные элементы становились flex-элементами.

UPD: На 04.02.2018 главная проблема с display: contents в том, что "редкий браузер долетел до середины Днепра", поддержка у свойства - отсутствует. Следите за обновлениями https://caniuse.com/#feat=css-display-contents

Как Вы могли увидеть, CSS Grid Layout - это часть Вашего инструментария. Не бойтесь смешивать его с другими методами создания макетов, чтобы получить различные эффекты. И не переключайтесь, дальше будет много интересного.

Метки документа и участники

Внесли вклад в эту страницу: coshturnina, InkFaust
Обновлялась последний раз: coshturnina,