6 Августа 2019

Поэтапное руководство по алгоритму автоматического размещения из CSS Grid

Источник - https://habr.com/ru/company/nix/blog/326098/

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

В этом руководстве мы рассмотрим все этапы алгоритма автоматического размещения элементов из модуля CSS Grid Layout. Каждый этап управляется с помощью свойства grid-auto-flow. В своих других статьях “Introducing the CSS Grid Layout” и “Seven Ways You Can Place Elements Using CSS Grid Layout” автор рассмотрел спецификацию CSS Grid и проанализировал, как с помощью Grid можно позиционировать элементы на веб-странице. Однако в этих материалах в сетке явным образом задавалась позиция единственного элемента. Остальные элементы размещались с помощью некоего алгоритма.

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

Основные принципы


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

  • Анонимные элементы сетки (Anonymous grid items). Если поместить текст напрямую в контейнер сетки, без обёртывания в какой-либо тег, то он превратится в анонимный элемент сетки. К таким элементам нельзя применять стили, но при этом они наследуют правила стилей от родительских контейнеров. Обратите внимание, что white space внутри контейнера сетки не приводит к созданию анонимного элемента.

  • Значение сеточного интервала (Value of grid spans). В отличие от позиционирования в сетке, алгоритм не содержит отдельных правил определения значения интервала в сетке. Если значение не задано, то по умолчанию оно равно 1 (элемент занимает только свою ячейку).

  • Неявная сетка (Implicit grid). Сетка, построенная на основе значений свойств grid-template-rowsgrid-template-columns и grid-template-areas, называется явной сеткой (explicit grid). Если теперь определить позицию элемента так, чтобы он находился вне границ явной сетки, то браузер сгенерирует дополнительные строки сетки, которые будут захватывать этот элемент. Эти строки в совокупности с явной сеткой формируют неявную сетку. Подробнее об этом можно почитать в “Where Things Are at in the CSS Grid Layout Working Draft”. Алгоритм авторазмещения также может стать причиной появления дополнительных столбцов или рядов в неявной сетке.

Теперь нужно отметить вот что. Значение по умолчанию свойства grid-auto-flow, с помощью которого мы управляем алгоритмом, равно row. Это же значение будет использоваться в последующем объяснении работы алгоритма. Если же явно задаете свойству значение column, то не забудьте в этом объяснении заменить все экземпляры термина row на column. К примеру, «Размещение элементов с помощью настройки row-позиции, а не column» превратится в «Размещение элементов с помощью настройки column-позиции, а не row».

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

Этап #1: Генерирование анонимных элементов сетки


Первое, что делает алгоритм, когда пытается поместить все элементы в сетку, это создаёт анонимные элементы. Как упоминалось выше, вы не можете применять стили к таким элементам, потому что просто не к чему их применять.

Следующий код генерирует анонимный элемент сетки из межэлементного текста (inter-element text):

<div class="container">
  <span class="nonan">1</span>
    Anonymous Item
  <div class="nonan floating">2</div>
  <div class="nonan">3</div>
  <div class="nonan floating">4</div>
  <div class="nonan">5</div>
</div>

В этом примере нужно отметить ещё и то, что алгоритм игнорирует CSS float’ы, применённые к div 2 и div 4.

→ Демо на CodePen

Этап #2: Размещение элементов на явно указанные позиции


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

<div class="container">
  <div class="item a">A</div>
  <div class="item b">B</div>
  <div class="item c">C</div>
  <div class="item d">D</div>
  <div class="item e">E</div>
  <div class="item f">F</div>
  <div class="item f">G</div>
  <div class="item f">H</div>
  <div class="item f">I</div>
</div>

Первыми в сетку будут помещены элементы, для которых явно указаны позиции. В нашем случае пусть это будут элементы А и В. Пока что проигнорируем все остальные. Зададим позиции для А и В:

.a {
  grid-area: 1 / 2 / 2 / 3;
}

.b {
  grid-area: 2 / 1 / 4 / 3;
}



Алгоритм размещает A и B в соответствии с их значениями свойства grid-area:

  • На основе первого и второго значений grid-area задаёт для обоих элементов позицию левого верхнего угла.
  • На основе третьего и четвёртого значений grid-area задаёт для обоих элементов позицию правого нижнего угла.

→ Демо на CodePen

Этап #3: Размещение элементов с помощью настройки row-позиции, а не column


Теперь алгоритм размещает элемент с явно указанными row-позициями в свойствах grid-row-startи grid-row-end

Зададим значение grid-row для элементов C и D:

.c {
  grid-row-start: 1;
  grid-row-end: 3;
}

.d {
  grid-row-start: 1;
  grid-row-end: 2;
}

Чтобы определить column-позицию, которая не задана явным образом, алгоритм действует по одному из двух сценариев в соответствии с режимом размещения (packing mode): 

  • Разреженное размещение (sparse packing) (по умолчанию).
  • Плотное размещение (dense packing).

Разреженное размещение на этапе #3


Это поведение по умолчанию. Начальная строка столбца (column-start line) нашего элемента получит минимальный возможный индекс строки (line index). Так мы избежим взаимного наложения пространства для текущего элемента и ячеек, уже занятых другими элементами.

Начальная строка столбца также должна идти после элемента, уже размещённого в том же ряду на этом этапе. Подчеркнём: на этом этапе, а не до этого этапа.



Рассмотрим на примере: элемент D не перемещался влево от А, даже при том, что он мог бы туда поместиться без каких-либо наложений. Дело в том, что элементы с явно заданной row-позицией, а не column-, алгоритм не размещает перед другим, аналогично позиционированным в данном ряду элементом (в нашем примере — С). Если у элемента С убрать значения grid-row, тогда D переместится влево от А.

Иными словами, элемент D, у которого явно задана row-позиция, а не column, может быть помещён перед А, но только в том случае, если этому не мешает С. А в данном случае он мешает, потому что у С, как и у D, определена row-позиция, а не column, и он находится в том же ряду, что и D.

→ Демо на CodePen

Плотное размещение на этапе #3


Если вам нужно заполнить элементом D пустое место перед A, то придётся присвоить свойству grid-auto-flow значение row dense.

.container {
  grid-auto-flow: row dense;
}



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

→ Демо на CodePen

Этап #4: Определение количества столбцов в неявной сетке


Далее алгоритм пытается определить количество столбцов в неявной сетке. Это происходит так:

  • Алгоритм берёт количество столбцов в явной сетке.
  • Затем проходит по всем элементам с заданной column-позицией и добавляет столбцы в начало и конец явной сетки, чтобы охватить все элементы.
  • Потом алгоритм проходит по всем элементам без заданной column-позиции. Если самое большое значение интервала (span) у одного из них оказывается больше ширины неявной сетки, то алгоритм добавляет столбцы в конце, чтобы охватить этот интервал.

Этап #5: Размещение оставшихся элементов


К данному моменту алгоритм уже разместил все элементы, чьи позиции явно определены, а также элементы с известными row-позициями. Теперь он начинает размещать в сетке оставшиеся элементы.

Но прежде чем рассматривать этот процесс, введём новый термин: курсор авторазмещения (auto-placement cursor). Это текущая точка вставки в сетку, определяемая пересечением пары координат — ряда и столбца. Изначально курсор помещается в точку пересечения начальных ряда и столбца неявной сетки. 

Напомним, что позиционирование элементов зависит от режима размещения (packing mode), задаваемого свойством grid-auto-flow.

Разреженное размещение на этапе #5


По умолчанию оставшиеся элементы размещаются в разреженном режиме. Вот как это происходит.

Если для элемента не задана позиция ни по одной оси:

  • Алгоритм инкрементирует column-позицию курсора до тех пор:
    а) пока не возникнет наложения между текущим элементом и ранее размещёнными,
    б) либо пока сумма «значение column-позиции курсора + значение column-интервала элемента» не превысит количество столбцов неявной сетки.

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

Если для элемента задана column-позиция:

  • Значение column-позиции курсора присваивается значению строки column-start элемента. Если значение новой позиции получается меньше, чем предыдущая column-позиция курсора, то row-позиция увеличивается на 1.

  • Далее row-позиция увеличивается на 1 до тех пор, пока не будет достигнуто значение, при котором элемент не накладывается ни на одну из уже занятых ячеек. При необходимости в неявную сетку могут добавляться дополнительные ряды. Теперь значение начальной строки ряда элемента присваивается row-позиции курсора, а конечная строка ряда элемента задаётся в соответствии с его интервалом.

Чтобы было понятнее, рассмотрим на примере.



Размещение элементов E и F, когда не заданы позиции ни по одной оси


При обработке элемента E, у которого не заданы ни column-, ни row-позиция, для курсора задаются значения row 1 и column 1. Элемент E занимает только одну ячейку, он может поместиться в левый верхний угол без наложений. То есть алгоритм просто помещает элемент E на позицию row 1 / column 1.

Следующий элемент без заданных позиций по обеим осям — F. Значение column-позиции курсора увеличивается до 2. Но позиция row 1 / column 2 уже занята элементом А. Алгоритму приходится снова увеличивать значение column-позиции, пока оно не достигнет 4. Больше столбцов нет, и тогда row-позиция курсора увеличивается на 1, а column-позиция сбрасывается до 1: row 2 / column 1. Алгоритм снова начинает увеличивать column-позицию на 1, пока не дойдёт до 4. Место с координатами row 2 / column 4 пока что свободно и может быть занято элементом F. Алгоритм помещает его и переходит к следующему элементу.

Размещение элементов G и H, когда задана column-позиция


Начнём с G. Значение column-позиции курсора определяется таким же, как свойство grid-column-start элемента G — 3. Поскольку они меньше, чем предыдущее значение column (4), то row-позиция увеличивается на 1. То есть становится row 3 / column 3. Пространство с такими координатами в данный момент свободно, и G может быть туда помещён без наложений, что алгоритм и делает. Затем всё то же самое повторяется для элемента H.

→ Демо на CodePen

Плотное размещение на этапе #5


Когда свойству grid-auto-flow присваивается значение row dense, выполняется иной порядок действий. Если вставляемый элемент не имеет определённой позиции, то текущая позиция курсора определяется в соответствии со строкой на пересечении начальных ряда и столбца неявной сетки, до того, как будет определена позиция элемента.



Элемент I помещён слева от H, потому что позиция курсора сбрасывается на строку, находящуюся на пересечении начальных ряда и столбца неявной сетки, вместо того чтобы начинать с последнего размещённого элемента. Алгоритм ищет подходящую позицию без наложений, находит место слева от H и помещает там элемент.

→ Демо на CodePen

Заключение


В этой статье мы прошли по всем этапам работы алгоритма автоматического размещения из модуля CSS Grid Layout. Этот алгоритм управляется с помощью свойства grid-auto-flow.

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