Подробно о размещении элементов в грид-раскладке (CSS Grid Layout)

Перевод статьи Deep Dive into Grid Layout Placement с сайта blogs.igalia.com, опубликовано на css-live.ru с разрешения автора — Мануэля Рего Касановаса.

Полный обзор разных методов позиционирования элементов, предлагаемых спецификацией CSS Grid Layout.

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

  • Поддержка неявного грида перед явным. Так, чтобы к гриду можно было добавлять полосы не только в направлении заполнения (обычно справа/снизу), но и с противоположной стороны.
  • Правильная интерпретация неизвестных именованных грид-линий. Это случай, когда элемент привязывается к линии под названием «foo», но нигде в гриде нет линий с таким именем.

Эти задачи могут показаться простыми, но из них вытекало немало изменений в реализации самого механизма. Ради их завершения мне в итоге пришлось чуть ли не переписать весь код нахождения позиций в гриде. Я даже написал документ с пояснением всего плана и всех технических подробностей, где можно найти ссылки на все соответствующие патчи.

Теперь, когда я всё это доделал, самое время объяснить, как им пользоваться. Хотя мой коллега Серхио уже писал об этом в 2014-м, спецификация с тех пор поменялась, так что, думаю, лучше попытаться объяснить всё целиком с нуля. Эта статья — что-то вроде выжимки с примерами из раздела «Размещение грид-элементов» спецификации CSS Grid Layout.

Грид-линии

Это, пожалуй, одно из важнейших понятий спецификации грид-раскладки. Грид-линии — это линии, разделяющие грид по горизонтали и вертикали. И они нумеруются, начиная с 1.

Рассмотрим, как это работает, на примере грида 3х2:

<div style="display: grid;
grid-template-columns: 300px 200px 100px;
grid-template-rows: 100px 50px;">
</div>

8

Пример нумерованных грид-линий

Свойства размещения в гриде

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

  • grid-column-start: задает первую вертикальную линию для элемента.
  • grid-column-end: задает последнюю вертикальную линию для элемента.
  • grid-row-start: задает первую горизонтальную линию для элемента.
  • grid-row-end: задает последнюю горизонтальную линию для элемента.

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

Начальное значение этих свойств — auto, благодаря чему элементы могут размещаться автоматически, отыскивая в гриде пустые ячейки. За подробностями этого обращайтесь к предыдущей статье по теме (либо к недавней статье на css-live по той же теме — прим. перев.).

В добавок к этому есть несколько удобных сокращений:

  • grid-column: сокращенная запись для свойств grid-column-start и grid-column-end.
  • grid-row: сокращенная запись для свойств grid-row-start и grid-row-end.
  • grid-area: сокращенная запись для всех 4 свойств сразу..

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

<div style="grid-column-start: 2; grid-column-end: 4;
grid-row-start: 1; grid-row-end 2;">
</div>

Возможно, читать будет проще, если воспользоваться сокращениями:

<div style="grid-column: 2 / 4; grid-row: 1 / 2;"></div>

Это значит, что грид-элемент займет 2-ю и 3-ю колонки в первом ряду.

5

Пример размещения элемента с помощью номеров линий

Охват ячеек

Предыдущий грид-элемент охватывает 2 колонки (2-ю и 3-ю), ссылаясь на линии. То же самое можно сделать с помощью ключевого слова span, в сочетании с количеством ячеек, которое нужно охватить.

Таким образом, поместить элемент в то же самое место можно так:

<div style="grid-column: 2 / span 2; grid-row: 1;"></div>

5

Пример размещения элемента с помощью span

Обратите внимание, что здесь мы не указываем последнюю линию для ряда. Это значит, что у grid-row-end будет значение auto. В этом случае auto по умолчанию означает охват одной ячейки.

Отрицательные номера линий

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

Отталкиваясь от того же примера, можно опять поместить элемент в то же самое место с помощью отрицательных индексов:

<div style="grid-column: -3 / -1; grid-row: -3 / -2;"></div>

5

Пример размещения элементов с помощью отрицательных номеров

В некоторых случаях это бывает очень кстати. Например, если элемент обязательно должен быть в последней колонке, независимо от общего количества полос, достаточно лишь задать grid-column-end: -1;.

Именованные грид-линии

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

Немного изменим наше определение грида, сохранив размеры полос, но добавив линиям имена:

<div style="display: grid;
grid-template-columns: [start] 300px [main] 200px [aside] 100px [end];
grid-template-rows: [top] 100px [middle] 50px [bottom];">
</div>

И снова, чтобы разместить элемент в том же самом месте, нужно лишь обратиться к именам линий:

<div style="grid-column: main / end; grid-row: top / middle;"></div>

5

Пример размещения по именам линий

У одной линии может быть несколько имен, надо только задать их в определении: grid-template-rows: [top start] 100px [middle center] 50px [bottom end];.

Имена линий также могут повторяться. Чтобы обратиться к ним, придется использовать номер, который тоже может быть положительным и отрицательным. Рассмотрим это на другом примере:

<div style="display: grid;
grid-template-columns: [col] 200px [gap] 50px [col] 200px [gap] 50px [col] 200px [gap];">
</div>

И представьте, что вы размещаете несколько элементов вроде этого:

<div style="grid-column: col 2;"></div>
<div style="grid-column: col 1 / gap 2;"></div>
<div style="grid-column: col -1;"></div>

5

Пример размещения элементов с повторяющимися именами линий

И конечно, можно охватывать ячейки вплоть до именованной линии:

<div style="grid-column: col 2 / span 2 gap;"></div>

5

Пример размещения элемента с охватом ячеек до именованной линии

Грид-области

Что еще лучше, можно определять грид-области и размещать элементы непосредственно в них. Понадобится свойство grid-template-areas, чтобы присвоить имена различным областям в гриде. И можно непосредственно размещать элементы, пользуясь сокращенным свойством grid-area.

Давайте для примера возьмем грид побольше (5×4):

<div style="display: grid;
grid-template-columns: 100px 100px 100px 100px 100px;
grid-template-rows: 100px 100px 100px 100px;
grid-template-areas:
'title title title title aside'
'nav main main main aside'
'nav main main main aside'
'footer footer footer footer footer';">
</div>

И разместим по элементу в каждой области:

<div style="grid-area: title;"><div>
<div style="grid-area: nav;"></div>
<div style="grid-area: main;"></div>
<div style="grid-area: aside;"></div>
<div style="grid-area: footer;"></div>

5

Пример размещения элементов в грид-областях

Грид-области и именованные грид-линии

У областей и размещения в них есть еще одна интересная особенность: грид-области неявно задают имена окружающим их линиям. Эти неявные имена отличаются суффиксами «-start» и «-end». И при размещении элементов можно обращаться к этим линиям, а не сразу к целой области.

Например, область «title» из предыдущего примера создает 4 неявных имени для линий (по 2 для каждой оси):

  • Линия слева: «title-start»
  • Линия справа: «title-end»
  • Линия сверху: «title-start»
  • Линия снизу: «title-end»

Разместить элемент с помощью неявных имен можно по образцу следующего примера:

<div style="grid-column: main-start / aside-end;
grid-row: title-start / nav-end;"></div>

5

Размещение элемента с помощью неявных имен от грид-области

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

<div style="display: grid;
grid-template-columns: [title-start nav-start footer-start] 100px [main-start nav-end] 100px 100px 100px [aside-start title-end main-end] 100px [aside-end footer-end];
grid-template-rows: [title-start aside-start] 100px [nav-start main-start title-end] 100px 100px [footer-start nav-end main-end aside-end] 100px [footer-end];">
</div>

Все примеры размещения элементов из этого раздела в этом новом гриде разместятся в абсолютно тех же самых местах.

Неявный грид

Свойствами для задания грида (grid-template-columns, grid-template-rows и grid-template-areas) мы определяем явное количество грид-полос (колонок и рядов) в нашем гриде. Однако спецификация гридов разрешает размещать элементы и вне явного грида. Ради поддержки этой возможности автоматически создаются неявные полосы, размер которых управляется свойствами grid-auto-columns и grid-auto-rows. В следующих примерах я буду выделять неявные линии красным цветом.

В этот раз возьмем простой грид 2×1:

<div style="display: grid;
grid-template-columns: 200px 200px;
grid-auto-columns: 100px;">
</div>

И представим, что мы размещаем элемент в 5-ю колонку (grid-column: 5;). Поскольку у грида лишь 2 колонки, для размещения элемента будут добавлены еще 3 неявные.

5

Пример неявного грида

Опять же, можно создавать неяные полосы с элементами, охватывающими несколько ячеек. Например, если элемент занимает 3 колонки, начиная со второй (grid-column: 2 / span 3):

5

Пример неявного грида с охватом нескольких ячеек

Изначально неявные полосы могли добавляться только с конца. Но теперь можно добавлять их и перед явным гридом. Например, если мы разместим элемент с помощью grid-column: -5;, это добавит 2 колонки слева и элемент будет помещен в минус вторую колонку.

5

Пример неявного грида перед явным

Неявный грид и именованные грид-линии

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

Этот простой пример размещает элементы, обращаясь к несуществующей линии с названием «foo». Для примера мы создадим 3 неявных колонки (1 перед и 2 после явного грида) вот такими элементами:

<div style="grid-column: foo;"></div>
<div style="grid-column: 2 / span 2 foo;"></div>
<div style="grid-column: -1 foo;"></div>

5

Пример неявного грида с использованием неизвестных именованных грид-линий

Обратите внимание, что простейший пример с grid-column: foo оказался в 4-й колонке (добавив лишнюю пустую колонку сразу после явного грида). Это потому, что первой линией с именем «foo» считается первая неявная линия (линия 4), так что последняя линия грида (линия 3) сюда не включается.

Кроме того, последний элемент с grid-column: -1 foo попал в минус первую колонку (возможно, неожиданно для вас). Это потому, что мы начинаем искать линию с именем «foo» начиная от края явного грида. Таким образом, мы пропускаем линии -1, -2 и -3 (поскольку они явно не «foo») и считаем, что это имя принадлежит линии -4 (первая линия неявного грида).

Всё несколько хитрее, если такая линия на самом деле есть, поскольку ее тоже нужно учесть при размещении элемента. Особенно сложно это в случае, если вы используете span до именованной линии, но линий не хватает. В этом случае только неявные линии с той стороны, в направлении которой мы их ищем, могут считаться линиями с искомым именем.

Опять же, надеюсь, это проще будет представить наглядно. Давайте добавим имя средней линии в предыдущем примере:

<div style="display: grid;
grid-template-columns: 200px [middle] 200px;
grid-auto-columns: 100px;">
</div>

А теперь разместим несколько элементов, обращаясь к этой линии “middle”:

<div style="grid-column: 3 middle;"></div>
<div style="grid-column: span 2 middle / 5;>"</div>
<div style="grid-column: -4 / span middle;"></div>

5

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

Странным случаем здесь оказывается grid-column: span 2 middle / 5;, как видно, он занимает место от минус первой по 4-ю колонки (включительно). Элемент заканчивается на линии 5, и ему приходится захватить 2 линии с именем «middle», чтобы найти начало. Может показаться, что это должны быть линии 2 и 4, но, как уже объяснялось, линии должны отсчитываться от края явного грида. Так что сначала мы посчитаем линию 2, а потом нам придется искать начало элемента среди неявных линий слева (в нашем случае это линия -4).

Особые случаи

Со свойствами размещения в гридах бывают особые ситуации, для которых предусмотрен раздел спецификации о разрешении конфликтов.

Например, если вы размещаете элемент, у которого линия конца находится перед линией начала, то эти линии меняются местами. То есть что-то вроде grid-column: 5 / 2; превратится в grid-column: 2 / 5;.

Другой случай — когда span указан и для начальной, и для конечно позиции. Тогда span для конечной позиции отбрасывается. Так, grid-column: span 2 / span 3; превратится в grid-column: span 2;. Что задействует алгоритм автоматического размещения, чтобы найти свободное место (в данном случае шириной в 2 колонки) и расположиться в нем.

Последний случай — когда у нас указан только span до именованной линии. В этом случае он заменяется на span 1. Например, grid-column: span foo; превратится в grid-column: span 1;.

5

Пример особых случаев размещения

Итого

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

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

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

Вот определение грид-контейнера, использованного в этом примере:

<div style="display: grid;
grid-template-columns: [left] 200px [main-start] 100px [center] 100px 100px [main-end] 50px 50px [right];
grid-template-rows: [top title-start] 50px [title-end main-start] 200px [main-end center] 150px 100px [bottom];
grid-auto-columns: 50px;
grid-auto-rows: 50px;">
</div>

А на следующей картинке видно, как разместятся разные элементы.

5

Большой пример размещения

Вы можете пощупать его «вживую» в нашем репзитории для примеров: http://igalia.github.io/css-grid-layout/grid-placement.html

Состояние дел

Как отмечено во вступлении, реализация в Blink сейчас (Chrome 50+) должна поддерживать все эти возможности размещения. Над этой реализацией работает Igalia, сейчас мы портируем ее и в WebKit тоже.

С другой стороны, у Gecko тоже уже есть их поддержка, которую разработала сама Mozilla.

5

Igalia и Bloomberg вместе работают над тем, чтобы сделать веб лучше

Наконец, как всегда я хочу еще раз подчеркнуть, что вся эта работа проделана в рамках сотрудничества между Igalia и Bloomberg. Большое спасибо за поддержку!

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

10 Комментарии

  1. JeStone

    Очень интересная статья, спасибо. Неявные гриды с автоименованием та еще головоломка =)

    У меня вопрос по вот этому примеру из раздела «Именованные грид-линии»
    http://css-live.ru/Primer/grids/example-named-grid-lines-items.svg

    Не могу понять как у автора получилось, что 3й элемент расположился в нижнем ряду, ведь насколько я понимаю расположение элементов идет слева-направо сверху-вниз (если забыть про grid-auto-flow: dense, конечно) и в Chrome 48 я ожидаемо получил 3й элемент справа от 2го
    https://jsfiddle.net/zzk4dfzf/
    Я что-то упустил?

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

      Спасибо за внимательность и отличный вопрос! Действительно, я тоже не вижу оснований для нового ряда, и в Фоксах (44-м с флагом и ночном 47-м) он тоже во втором ряду. Уточним у автора оригинала:)

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

      Оказалось, что это действительно была ошибка автора. Еще раз спасибо за наблюдательность!

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

      Исправили и перевод, и оригинал:)

  2. Rinat

    Суперская статья, спасибо, многое теперь стало понятнее.

  3. Артём

    Объясните, пожалуйста, правило: #item-7 { grid-column: span bar / 9; }.
    Что такое bar? Я думал, что это название грид-линии, но линий с таким именем не задавалось.

    И еще вопрос по:

    Пример размещения элемента с охватом ячеек до именованной линии

    А именно:
    Почему здесь написано так, а не, например, вот так:

    То есть, я правильно понимаю, в первом случае алгоритм такой: «Мы размещаем элемент во 2й колонке, захватываем при этом 2 колонки, НО, если последняя линия, которая ограничивает элемент НЕ gap, то продолжаем охватывать ячейки дальше, пока gap не будет являться завершающей грид-линией для элемента»?

    А в моем случае(вариант ниже): «Мы размещаем элемент во 2й колонке, захватываем при этом ВСЕ колонки, которые встреться перед грид-линией gap»?

    P.S. Тут можно видеть, что Grid Layout развязывает руки и фантазию перед безграничным разнообразием вариантов решения одной и той же задачи, однако, хотелось бы прояснить ситуацию в деталях.

    P.S.S. Одно дело, когда grid-column-start, grid-row-start, etc заменяют простым grid-area, это вопрос вкусовщины и, если хотите, читабельности, но в примерах выше, что я навел, мне кажется за вариациями воплощения одинаковых вещей кроются разные алгоритмы.

    1. Артём

      Почему здесь написано так, а не, например, вот так:


    2. Артём

      <div>style="grid-column: col 2 / span 2 gap;"></div>
      <div>style="grid-column: col 2 / span gap;"></div>

      Разобрался с экранированием=)

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

      Спасибо за хороший вопрос!)

      По первому вопросу — насколько я сам понимаю, пример с #item-7 { grid-column: span bar / 9; } по сути аналогичен примеру с grid-column: span 2 middle / 5 из статьи. Конец элемента фиксирован, а начало приходится искать среди неявных грид-линий (поскольку, как верно замечено, явной грид-линии с именем bar нет). Здесь на ее роль годится первая неявная грид-линия слева (линия -8).

      По второму вопросу — достаточно близко: в первом случае (…/ span 2 gap;) мы захватываем ячейки по вторую линию с именем gap (если линий с таким именем не хватает, в ход идут неявные линии после грида), а во втором (…/ span gap;) — только по первую такую линию.

      1. Артём

        Спасибо за ответ. Некоторые хитрости не совсем очевидны. Например.
        <div style="grid-column: 3 middle;"></div>
        <div style="grid-column: middle 3;"></div>

        Поскорее бы Grid Layout вошел в обиход, даже на первый взгляд здесь нюансов чрезмерно много.

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

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

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

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