Мысли вслух о подсетках в CSS Grid Layout
Перевод статьи Subgrids thinking out loud с сайта blogs.igalia.com, опубликовано на css-live.ru с разрешения автора — Мануэля Рего Касановаса.
Итоги наших размышлений о функции подсеток из спецификации CSS Grid Layout.
За последние недели подсетки стали острой темой, в основном из-за статьи «Подсетки в Grid Layout жизненно важны» Эрика Мэйера (кстати, первод есть у нас на сайте — прим. перев.), в которой он привёл несколько примеров того, где подсетки могут оказаться весьма кстати. Эти примеры так или иначе связаны с предыдущей статьёй fantasai на эту же тему. К тому же Рэйчел Эндрю рассказывала про подсетки в своём блоге и в своей последней электронной книге про Grid Layout (что вполне достаточно, чтобы получить представление о синтаксисе и основных функциях грида).
Возможно вы в курсе, что последнее время Igalia работает над реализацией CSS Grid Layout в Chromium/Blink и Safari/WebKit. Как разработчики, мы решили, что неплохо будет поделиться нашим откликом на эту тему, так что на этой неделе команда, работающая над Grid Layout (Серхио, Хави и я) договорились о встрече для рассмотрения функции подсеток. Вот наши первые соображения об этом и первое черновое предложение насчет возможной реализации.
Состояние технологии
Функция подсеток позволяет создавать вложенные гриды, разделяющие линии с грид-контейнером. Мы просто определяем размер полос в главном грид-контейнере и подсетки используют эти размеры для раскладки своих элементов.
В грид-контейнере только непосредственные потомки могут быть грид-элементами и могут размещаться в гриде. Можно отметить один из этих элементов как подсетку, и тогда дочерние элементы подсеток будут как обычные элементы для главного грида. Это сложно объяснить на словах, поэтому в следующем разделе мы покажем несколько примеров.
С января 2014 эта функция в спецификации помечена «группой риска», а значит может быть отложена на следующий уровень (или версию) спецификации.
Сегодня ни одна из реализаций грида (ни в браузерных движках, ни в полифиллах) ещё не поддерживает подсетки. В основном потому, что это весьма сложная функция и разработчики отдают предпочтение другим частям спецификации.
Применения для подсеток
Для понимания подсеток разберём несколько применений. Стандартный пример для подсеток — простая форма, определённая с помощью списка:
<form> <ul> <li><label>Name</label><input></li> <li><label>Surname</label><input></li> <li><label>Location</label><input></li> <li><label>Email</label><input></li> </ul> </form>
В идеале хотелось бы определить грид с метками в первой и полями во второй колонках. Если определить элемент <ul>
в качестве грид-контейнера, то придётся определить <li>
как подсетки, чтобы была возможность размещать метки и поля как мы пожелаем. Без подсеток пришлось бы удалить список (вероятно, заменив <ul>
на <div>
и удалив теги <li>
), упрощая HTML и нарушая семантическую структуру, что на самом деле плохо с точки зрения доступности.
С поддержкой подсеток мы могли бы использовать следующий CSS:
ul { display: grid; grid-template-columns: auto 1fr; } li { display: grid; grid: subgrid; } label { grid-column: 1; } input { grid-column: 2; }
Пример формы с применением подсетки
В этом случае вместо подсеток мы могли бы использовать display: contents;
(который пока поддерживается только в Firefox). Просто применив display: contents;
к элементам <li>
мы получим тот же результат.
С другой стороны, ещё одним примером подсеток может послужить каталог товаров магазина. Представьте, что эти продукты состоят из заголовка, картинки и описания.
HTML для каждого пункта мог бы выглядить примерно так:
<div class="product"> <h1>Title</h1> <img src="product.jpg" /> <p>Long description...</p> </div>
И представьте, что мы взяли такой CSS:
body { display: grid; grid-template-columns: 1fr 1fr 1fr; } .product { display: grid; grid: subgrid; } h1 { grid-row: 2; } img { grid-row: 1; } p { grid-row: 3; }
Задача состоит в том, что продукты должны появляться в подсетке из трёх рядов, так, чтобы все заголовки и все описания были выровнены по горизонтали. Для этого определяем главный грид из трёх колонок и подсетки для каждого продукта с тремя рядами.
Пример каталога с помощью подсетки
Как видно на рисунке, даже если заголовок занимает две строки (или независимо от строк в описании), элементы выравниваются правильно.
Такой результат невозможно получить с display: contents;
, поскольку в этом случае все продукты окажутся в одном ряду (а в гриде фактически три ряда). Поэтому мы явно устанавливаем ряд для элементов <h1>
(второй ряд), <img>
(первый ряд) и <p>
(третий ряд). Без подсеткок ряды не смогли бы автоматически продолжиться на пятый, четвёртый и шестой ряды, когда продукты заполнили бы три колонки, определённые в главном гриде.
Основные проблемы
Этот и следующий разделы технические и их скорее надо было бы обсуждать с рабочей группой по CSS (CSS WG), но мы подумали, что примеры в статье блога будут куда нагляднее, чем обычная рассылка в www-style
.
Читая спецификацию и думая, как реализовать подсетки, мы столкнулись с некоторыми потенциальными проблемами. Вот некоторые из них в произвольном порядке.
Margin
, border
и padding
Возможно, самая главная преграда на пути к подсеткам — margin
, border
и padding
. И их влияние на алгоритм задания размеров грид-полосам.
Для объяснения возьмём простой пример, представьте грид 3х2, в котором подсетка полностью занимает первый ряд.
<div style="display: grid; grid-template-columns: auto auto auto; grid-template-rows: auto auto;"> <div style="display: grid; grid: subgrid; grid-column: 1 / 4; grid-row: 1 / 2;"> <div style="grid-column: 1;">Item 1</div> <div style="grid-column: 2;">Item 2</div> <div style="grid-column: 3;">Item 3</div> </div> </div>
Допустим ширина каждого элемента 100px, так что ширина каждой колонки будет 100px.
Однако, если установить для подсетки padding (напр. padding: 0px 50px;
), то для вычисления размера полос потребуются дополнительные действия:
- Поскольку первая колонка прилегает к левому краю подсетки, в расчет ее размера добавится 50px. В результате полоса будет 150px.
- На вторую колонку padding не повлияет, так что она останется 100px.
- С третьей колонкой та же ситуация, что и с первой, поскольку она прилегает к правому краю подсетки. Так что её итоговый размер равен 150px.
Пример подсетки с padding
Проблема в том, что алгоритм задания размеров грид-полосам работает непосредственно с грид-элементами. Поэтому, прежде чем использовать внешний отступ, границу и/или внутренний отступ для вычисления размеров полосы, нужно сначала узнать, находится ли элемент на одном из краев подсетки.
На первый взгляд ничего сложного, но если подумать об элементах, охватывающих несколько полос и вложенных подсетках, то всё усложняется в разы. Например, представьте вложенную подсетку, занимающую вторую и третью колонки в первом ряду с padding: 0px 25px;
.
Пример вложенных подсеток с padding
Для каждой колонки главного грид-контейнера придётся учитывать разные padding-и подсеток. Поэтому для каждого элемента нужно проверять не только, на краю подсетки ли сам элемент, но и всех его предков.
Кстати, полосы прокрутки в подсетках — аналогичная проблема для вычисления размера грид-полосы.
Неявные полосы в подсетке
Проблема тут в том, что происходит, когда мы обходимся без явных полос в подсетке. Согласно спецификации, элементы в этой ситуации должны использовать неявные полосы от подсетки, а не брать полосы от главного грида. А это значит, что они не выравниваются с помощью главного грида (они не разделяют с ним общие линии).
Возьмём пример, иллюстрирующий эту пробему:
<div style="display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px;"> <div>i1</div> <div>i2</div> <div>i3</div> <div style="display: grid; grid: subgrid; grid-column: 1 / 3; grid-row: 2 / 3;"> <div style="grid-column: 1;">si1</div> <div style="grid-column: 2;">si2</div> <div style="grid-column: 3;">si3</div> <div style="grid-column: 4;">si4</div> </div> </div>
Пример подсетки с неявными полосами
Как мы видим, элементы подсетки si3
и si4
размещаются в неявных полосах подсетки. Эти полосы никак не связаны с главным грид-контейнером, буквально это и сказано в спецификаци: «они фактически распространяются в третье измерение». Ума не приложим, для чего такая штука может понадобиться, и она кажется действительно странной, поскольку главная цель подсетки — разделить общие линии с главным грид-контейнером.
Кроме того, представьте другой пример колонки с автоматическим размером и некоторые элементы из подсетки в этой колонке, которые охватывают еще и что-то вне явной подсетки. Чтобы определить размер этой колонки с автоматическим размером главного грид-контейнера, алгоритму задания размеров грид-полосам пришлось бы очень попотеть.
Автоматический охват грида
В предыдущем примере мы определяли подсетки, явно устанавливая обе позиции в гриде (колонку и ряд, с которых начинается подсетка) и охват грида (сколько ячеек занимает подсетка в каждом направлении)
Однако, можно определить подсетку, просто установив позицию в гриде с автоматическим охватом грида. Согласно спецификации, неявные полосы подсетки будут определять, сколько ячеек она охватит.
Опять же, надеюсь, одного примера хватит для понимания:
<div style="display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px;"> <div style="display: grid; grid: subgrid; grid-column: 2; grid-row: 1;"> <div style="grid-column: 1;">si1</div> <div style="grid-column: 2;">si2</div> <div style="grid-column: 3;">si3</div> </div> </div>
Подсетка размещается в гриде, начиная со 2-й колонки и 1-го ряда. Затем первые два элемента ( si1
and si2
) размещаются в полосах из главного грида (вторая и третья колонки, соответствено). Но последний элемент ( si3
) оказывается без полосы, так что он оказывается в дополнительной неявной полосе в подсетке, не связанной с главным грид-контейнером. Поскольку подсетка не создает неявных полос в главном гриде,
Пример подсетки с автоматическим охватом грида
Это кажется весьма сложным для управления, особенно если подсетка не может создавать неявные полосы в главном грид-контейнере. Также, в сочетании с автоматическим размещением (где позиция в гриде не устанавливается) найти правильную позицию и охват крайне непросто.
Кроме того, представьте, что один из элементов в подсетке размещается с помощью grid-column: -1;
. Поскольку подсетка ещё не охватывает грид, вероятно, он разместится не в последней колонке, как мы ожидали, а в первой.
Подсетка только по одной оси
Ключевое слово subgrid
может устанавливаться для двух свойств, определяющих грид (grid-template-columns
и grid-template-rows
) или только одного из них. Если установить subgrid только для одного свойства, подсетка будет создана только в одном направлении.
Рассмотрим один пример:
<div style="display: grid; grid-template-columns: 150px 150px; grid-template-rows: 150px 150px;"> <div style="display: grid; grid-template-columns: subgrid; grid-column: 1 / 3; grid-row: 1 / 2;"> <div style="grid-column: 1;">si1</div> <div style="grid-column: 2;">si2</div> <div style="grid-column: 1;">si3</div> <div style="grid-column: 2;">si4</div> </div> </div>
В данном случае подсетка определяется только по направлению колонок, поэтому, чтобы высчитать размер элементов в первой и второй колонках, подсетка использует размеры полосы, уставновленные в главном грид-контейнере.
Однако, для рядов ничего не установлено, так что их размер определяется автоматически. Следовательно, ряды будут вести себя, как обычный вложенный грид, абсолютно независимый от родительского грид-контейнера.
Пример подсетки только по одной оси
Такое, наверное, можно реализовать, но опять же, функция необычна, и нужно хотя бы несколько внятных применений, чтобы пойти на это.
Навигация для потомков
Ещё одна проблема подсеток — бесконечный уровень вложенности. Поэтому для определения размера полосы в грид-контейнере придётся обойти не только непосредственных, но и всех остальных потомков. И если этим злоупотреблять, производительность пострадает непомерно.
Здесь, видимо, придётся просто жить с этим и обследовать всех потомков, платя за это производительностью.
Промежутки в гриде
Наверное, свойства для промежутков в гриде не стоит учитывать в подсетках. Нужно, чтобы элементы выравнивались с помощью грид-контейнера, поэтому разные промежутки выглядят весьма странно.
Этого нет в спецификации, и причина скорее всего в том, что подсетки были добавлены раньше промежутков, поэтому часть спецификации не обновлялась.
Что ещё
Многое мы еще не успели подробно рассмотреть. К примеру:
- Именованные грид-линии от главного грид-контейнера, используемые в подсетках.
- Позиционированные элементы в подсетках.
- Подсетки с перпендикулярными потоками
И мы уверены, что наверняка что-то ещё упустили.
Черновое предложение
Как видно, есть куча потенциальных проблем, и конечно они требуют тщательного анализа и дальнейших обсуждений, но в целом ощущается немалая сложность для реализации.
Поэтому, здесь можно подумать о более простом предложении. Франсуа Реми уже отправил одно в почтовую рассылку рабочей группы по CSS (CSS WG).
В попытках удовлетворить всем вышеописанным случаям и максимально избежать проблем, нас посетила ещё одна идея. Это предложение находится в статусе пре-альфа, так что относитесь к нему с долей скептицизма.
Идея в том, чтобы поддержка подсеток была ограниченной.
-
Делаем подсетку крайне похожей на
display: contents;
, поэтому ей нельзя устанавливать внешний отступ, границу и внутренний отступ. Это упростит расчёты, требуемые для определения размера полос и элементов.Основное отличие от
display: contents;
в том, что подсетка позволит распределять позиции в зависимости итогового размещения в главном грид-контейнере. Как в примере с каталогом магазина. -
Удаляем неявные полосы у подсетки, поэтому придётся всегда заранее устанавливать охват грида (сколько колонок и рядов занимает подсетка)
Элементы, пытающиеся разместиться в неявном гриде подсетки, в итоге окажутся в первой ячейке подсетки. Для элементов, охватывающих что-либо за пределами явного грида, охват будет усечён по краю подсетки.
- Очевидно, что элементы в подсетках придётся размещать перед элементыми в главном грид-контейнере. И как только все позиции будут рассчитаны, можно попытаться запустить алгоритм задания размеров грид-полосам в обычном режиме.
- Возможно, на начальном этапе подхода, разрешающего лишь подсетки по обеим осям, должно хватить.
Этого предложения вполне должно хватить для применений, описанных в первом разделе, нам понадобится многое уточнить в CSS, но они заработают.
Нам будет не хватать возможности устанавливать CSS-свойства для самих подсеток, но можно будет добавлять дополнительный элемент для подсетки в подсетку, охватывающий всю подсетку и рисующий границу, фон и всё необходимое. Решение конечно так себе.
Заключение
В этой статье в основном просто собраны рабочие соображения команды Igalia насчет CSS Grid Layout, по итогу целого дня обсуждения этой части спецификации. Мы просто хотим поделиться ими с остальным веб-сообществом, с рабочей группой по CSS и со всеми, кто не равнодушен к этой теме. Чтобы помочь поскорее решить судьбу подсеток в спецификации CSS Grid Layout.
Многие вопросы о подсетках по-прежнему без ответов:
- Отложить их до 2 уровня?
- Упростить их и оставить в 1 уровне?
- Оставить их как есть и убрать экспериментальный флаг для CSS Grid Layout только после реализации подсеток?
На наш взгляд, на эти вопросы пока нет подходящих ответов, но надеемся, что все вместе мы найдем правильное решение — решение, которым наконец окажутся довольны все заинтерсованные :)
И ещё кое-что, заслуживающее внимания: в конце месяца в Нью Йорке пройдёт мастер-класс по CSS Grid Layout, организованный одним из редакторов спецификации (fantasai). Мой коллега Серхио — разработчик кода для Grid Layout в Blink и WebKit — примет участие в этом событии. Надеюсь, там прояснятся эта и другие темы, и спецификация сможет перейти в стадию «Последнего рабчего черновика» (LCWD), став еще ближе к «Кандидату в рекомендации» (CR)!
Igalia и Bloomberg вместе работают над тем, чтобы сделать веб лучше
Наконец, мы хотим снова поблагодарить Bloomberg за поддержку Igalia в разработке CSS Grid Layout.
P.S. Это тоже может быть интересно:
Чуваки изобрели таблицы, не?
Они изобрели способ визуального расположения как в таблицах без таблиц (и привязки к их разметке).