Grid-парсер
Уникальная система описания CSS Grid раскладок через компактный строковый формат. JS-рантайм автоматически парсит атрибуты и генерирует CSS на лету.
Две дорожки, одна строка
Одна и та же строка a3b4-c2d5 раскладывается двумя
независимыми способами, и оба входят в griffincss-core.
Выбор — ваш, не библиотеки.
Рантайм — атрибут в разметке. Ничего собирать не нужно, раскладка появляется по мере разбора страницы.
<script src="griffincss.js"></script>
<div data-gr-layout="a3b4-c2d5">
<div class="gr-area-a">A</div>
…
</div>
Компилтайм — миксин в SCSS. Ни строки JS на странице, имя класса задаёте вы.
@use 'griffincss-core/scss/grid-parser' as p;
@include p.gr-grid-layout('a3b4-c2d5', $name: 'page');
<div class="gr-l-page">
<div class="gr-area-a">A</div>
…
</div>
Обе дают один и тот же CSS — это держит тест, сверяющий их вывод побайтово. Разница одна: без JS раскладка из миксина есть, из атрибута — нет, и контент при этом виден — защиту от FOUC ставит сам рантайм, в статическом CSS её нет. Миксин разобран ниже, в разделе «SCSS-миксины», а что выбрать под свою задачу — и нужна ли строка вообще — в разделе «Когда раскладка строкой окупается».
Формат раскладки
Раскладка описывается строкой, где - разделяет ряды, буквы задают имена grid-областей, а цифры — количество повторений. Символ _ создаёт пустую ячейку.
| Элемент | Назначение | Пример |
|---|---|---|
- | Разделитель рядов | a3b4-c2d5 → 2 ряда |
| Буквы | Имя grid-области: начинается с латинской буквы, дальше буквы и _ | hdr, main, side_a |
| Цифры | Количество повторений предыдущего имени | a3 → a a a |
_ | Пустая ячейка (в CSS: .) | _3 → . . . |
Имя читается до первой цифры, поэтому abcd — одна область
с именем abcd, а не четыре. Четыре области в ряд —
a1b1c1d1: цифра закрывает имя. Так и записываются многобуквенные
имена — hdr4-main4; одна буква без цифры допустима только
последней в ряду (a3b — область b в одну ячейку).
Раскладка проверяется до генерации CSS: счётчик без имени, нулевой счётчик, счётчик
больше 64, имя длиннее 32 символов, недопустимые символы и пустой ряд отвергаются.
Такой контейнер пропускается целиком, а в консоль уходит Griffincss: … —
полный список правил на странице JS-рантайм.
Базовое использование
<div data-gr-layout="a3b4-c2d5">
<div class="gr-area-a">A</div>
<div class="gr-area-b">B</div>
<div class="gr-area-c">C</div>
<div class="gr-area-d">D</div>
</div>
Сгенерированный CSS:
/* класс .gr-l-<хеш> рантайм вешает на контейнер сам */
.gr-l-k7gi9w {
display: grid;
grid-template-areas: "a a a b b b b" "c c d d d d d";
grid-template-columns: var(--gr-l-cols, repeat(7, minmax(0, 1fr)));
grid-template-rows: var(--gr-l-rows, repeat(2, auto));
}
.gr-area-a { grid-area: a; }
.gr-area-b { grid-area: b; }
.gr-area-c { grid-area: c; }
.gr-area-d { grid-area: d; }
gap в этом правиле не пишется: отступ раскладкам даёт одно статическое
правило [class*="gr-l-"] из griffincss-core.css. Поэтому
модификаторы .gr-gap-sm / .gr-gap / .gr-gap-lg
перекрывают его на обычных правах — см. Grid-утилиты.
Именованные области
Имена могут состоять из нескольких букв — используйте осмысленные названия для семантических областей страницы.
<div data-gr-layout="hdr4-main4-side2_2-ftr4">
<div class="gr-area-hdr">Header</div>
<div class="gr-area-main">Main Content</div>
<div class="gr-area-side">Sidebar</div>
<div class="gr-area-ftr">Footer</div>
</div>
Пустые ячейки
Символ _ создаёт пустую ячейку (преобразуется в . в CSS). _3 — три пустых ячейки подряд.
Полезно для создания асимметричных раскладок и отступов.
<div data-gr-layout="hdr6-nav2hero4-news3_3-ftr6">
<div class="gr-area-hdr">Header</div>
<div class="gr-area-nav">Nav</div>
<div class="gr-area-hero">Hero</div>
<div class="gr-area-news">News</div>
<div class="gr-area-ftr">Footer</div>
</div>
Совет: пустые ячейки _ создают настоящие CSS Grid ячейки с . в grid-template-areas. Они занимают место в сетке и влияют на распределение колонок.
Многострочные области (прямоугольники)
Область может занимать несколько рядов, если она повторяется с одинаковым количеством колонок. Это соответствует требованию CSS Grid: именованная область должна быть прямоугольной.
<div data-gr-layout="hero3nav3-hero3side3-ftr6">
<div class="gr-area-hero">Hero (2 ряда × 3 колонки)</div>
<div class="gr-area-nav">Nav</div>
<div class="gr-area-side">Sidebar</div>
<div class="gr-area-ftr">Footer</div>
</div>
Важно: область на нескольких строках должна занимать одинаковые колонки во всех рядах (прямоугольник). Это фундаментальное ограничение CSS Grid. L-образные и T-образные формы недопустимы.
Ширины треков
Строка раскладки описывает области, а не ширины: a3b4 значит
«три доли одному, четыре другому», и по умолчанию все семь дорожек равны.
Это её единственный довод — читать её должно быть легче, чем
grid-template-areas. Ширины задаются там, где им и место, —
в CSS, двумя свойствами:
| Свойство | Значение по умолчанию |
|---|---|
--gr-l-cols | repeat(число колонок, minmax(0, 1fr)) |
--gr-l-rows | repeat(число рядов, auto) |
Работает на обеих дорожках — и у рантайма, и у миксина: свойство читает само правило раскладки, а кто его написал, значения не имеет.
<!-- Фиксированная боковая колонка и резиновая основная -->
<div data-gr-layout="a1b1" style="--gr-l-cols: 12rem 1fr">
<div class="gr-area-a">Меню</div>
<div class="gr-area-b">Содержимое</div>
</div>
<!-- Колонка с вилкой ширины: не уже 12rem и не шире 20rem -->
<div data-gr-layout="a1b1" style="--gr-l-cols: minmax(12rem, 20rem) 1fr">…</div>
<!-- Ряды: шапка и подвал по содержимому, середина забирает остаток -->
<div data-gr-layout="a1-b1-c1" style="--gr-l-rows: auto 1fr auto; height: 12rem">…</div>
У миксина то же самое — свойство пишется рядом с классом, который он завёл:
@include gr.gr-grid-layout('a1b1', $name: 'shell');
.gr-l-shell {
--gr-l-cols: 12rem 1fr;
}
Число треков обязано совпадать со строкой раскладки.
В a3b4 семь колонок, значит и в --gr-l-cols
их семь. Меньше — недостающие браузер дорисует неявными дорожками
по auto, больше — лишние останутся пустыми. Молчания здесь
нет: раскладка ломается видимо, а вычисленное значение
grid-template-columns показывает DevTools. Сторожа в рантайме
нет намеренно — он читал бы вычисленные стили каждого контейнера,
и эта цена выше цены ошибки.
Значение по умолчанию — minmax(0, 1fr), а не 1fr:
доля не бывает уже своего содержимого, и одно длинное слово или широкая
таблица раздували бы дорожку, вынося сетку за контейнер. С нулевым
минимумом содержимое прокручивается внутри дорожки, а раскладка стоит.
Адаптивные раскладки
Атрибуты data-gr-layout-{sm,md,lg,xl} задают раскладку для соответствующего брейкпоинта.
JS-рантайм генерирует непересекающиеся @media-диапазоны. Элементы, чьи grid-area отсутствуют в текущем шаблоне, автоматически скрываются.
Скрытие — по замыслу, но не по ошибке. Область, которой нет
в части наборов, — обычный приём: сайдбар есть на десктопе и спрятан на телефоне.
Область, которой нет ни в одном наборе контейнера, — ошибка разметки
(чаще всего опечатка в имени или abcd вместо a1b1c1d1):
ребёнок с таким gr-area-* исчез бы на любой ширине молча, поэтому
рантайм пишет в консоль Griffincss: area "…" is not in any layout of its
container — один раз на контейнер и имя.
<div data-gr-layout="a1b1"
data-gr-layout-md="a3b4-c2d5"
data-gr-layout-lg="hdr4-main4-side2_2-ftr4">
<div class="gr-area-a">A</div>
<div class="gr-area-b">B</div>
<div class="gr-area-c">C</div>
<div class="gr-area-d">D</div>
<div class="gr-area-hdr">Header</div>
<div class="gr-area-main">Main</div>
<div class="gr-area-side">Sidebar</div>
<div class="gr-area-ftr">Footer</div>
</div>
Брейкпоинты по умолчанию (переопределяются через CSS-переменные):
| Брейкпоинт | Атрибут | Значение | CSS-переменная |
|---|---|---|---|
| Базовый | data-gr-layout | — | — |
| sm | data-gr-layout-sm | 640px | --gr-bp-sm |
| md | data-gr-layout-md | 768px | --gr-bp-md |
| lg | data-gr-layout-lg | 1024px | --gr-bp-lg |
| xl | data-gr-layout-xl | 1280px | --gr-bp-xl |
Те же брейкпоинты доступны и по ширине контейнера, а не окна —
атрибуты data-gr-layout-c{sm,md,lg,xl}. Разница в одну букву,
диапазоны строятся так же, а вместо @media рантайм пишет
@container. Подробности и оговорки — на странице
«Контейнерные запросы».
Непересекающиеся диапазоны: в отличие от простого накопления min-width,
griffincss описывает каждый брейкпоинт диапазоном в range-синтаксисе:
(width < 768px), (768px <= width < 1024px), (width >= 1024px).
Правило базовой раскладки действует до первой адаптивной, промежуточные — в своих диапазонах,
а последняя — от своего значения и выше. Арифметики -1px нет, поэтому щелей
на дробных ширинах не остаётся, а единицы измерения брейкпоинта могут быть любыми.
Границы считаются только по тем атрибутам, которые есть на элементе: если задан один
data-gr-layout-lg, база действует до 1024px, а не до 640px.
Авто-присвоение grid-area
Если дочерним элементам не указан класс gr-area-*, JS-рантайм автоматически присваивает имена областей в порядке документа.
Имена берутся из базового data-gr-layout (без суффикса брейкпоинта) в порядке первого появления
в раскладке. Имена, уже проставленные детям вручную, из раздачи исключаются, поэтому дубликаты невозможны —
даже если область занимает несколько рядов. Подробности — на странице JS-рантайм.
<div data-gr-layout="a2b2c2-a2d2e2">
<div>A — 2×2 на 2 ряда</div>
<div>Карточка B</div>
<div>Карточка C</div>
<div>Карточка D</div>
<div>Карточка E</div>
</div>
Карточка B
Ряд 1, колонки 3–4
Карточка C
Ряд 1, колонки 5–6
Карточка D
Ряд 2, колонки 3–4
Карточка E
Ряд 2, колонки 5–6
Вложенные раскладки
Grid-контейнеры можно вкладывать друг в друга без ограничений. Каждый контейнер изолирует свои grid-area имена — конфликтов между родителем и потомком не возникает. JS-рантайм генерирует каждое CSS-правило однократно, независимо от количества повторений.
<div data-gr-layout="a3b4-c2d5">
<div class="gr-area-a">A</div>
<div class="gr-area-b">
<div data-gr-layout="a1c3">
<div class="gr-area-a">Вложенный A</div>
<div class="gr-area-c">Вложенный C</div>
</div>
</div>
<div class="gr-area-c">C</div>
<div class="gr-area-d">D</div>
</div>
SCSS-миксины (компилтайм)
Альтернативный способ — сгенерировать раскладки на этапе компиляции SCSS через миксины. Это не требует JS-рантайма, но раскладки фиксируются в CSS.
@use 'griffincss-core/scss/grid-parser' as p;
// Один набор раскладок = один класс. Имя задаёте вы:
@include p.gr-grid-layout(
'a3b4-c2d5',
$md: 'hdr4-main4-side2_2-ftr4',
$lg: 'hero3nav3-hero3side3-ftr6',
$name: 'page'
);
<div class="gr-l-page">…</div>
Миксин генерирует тот же CSS, что и рантайм: непересекающиеся диапазоны
в range-синтаксисе, grid-template-areas, классы gr-area-* и правила
авто-скрытия. Разница одна: рантайм берёт имя класса из хеша набора
(.gr-l-<хеш>), а компилтайм — из $name.
Раздачу gr-area-* детям компилтайм не делает — проставьте классы в разметке.
Изменение в v0.7.0: миксины gr-grid-layout-sm/md/lg/xl удалены,
а gr-grid-layout() больше не принимает список раскладок — теперь это один вызов
на контейнер с аргументами $sm, $md, $lg, $xl
и обязательным $name. Правила больше не привязаны к значению атрибута
data-gr-layout.
Когда раскладка строкой окупается
Строка окупается там, где раскладка — переменная, а не
константа. Если она известна заранее и больше не меняется,
честнее обычный grid-template-areas или компилтайм-миксин:
ни JS, ни посредника. Ниже — четыре случая, в которых раскладка
становится переменной сама собой.
Раскладку выбирают данные, а не разработчик
Случай, ради которого рантайм и нужен: раскладка приходит строкой из базы, из поля конфига или из выбора редактора в админке. Обычным CSS это не решается, и вот почему.
| Способ | Чем ломается |
|---|---|
| Объявить все варианты классами заранее | Ограничивает редактора набором, который вы угадали |
Генерировать <style> на каждую страницу |
Это и есть рантайм, только свой: дубликаты правил, кеш, CSP |
style="grid-template-areas: …" |
Инлайн-стиль не умеет медиазапросы — адаптивности не будет |
Последняя строка — ограничение самого CSS, а не чей-то недосмотр.
Атрибут его снимает: a3b4-c2d5 — девять символов, которые
кладутся в колонку таблицы, ездят в JSON и подменяются без пересборки
стилей, а брейкпоинты при этом остаются на месте. Отсюда и класс задач:
конструкторы страниц, дашборды с перетаскиванием плиток, раскладка
под каждого арендатора в white-label, A/B-тест сменой одного атрибута.
Разметку присылает сервер
Фрагмент, пришедший по HTMX, Turbo или Livewire, несёт раскладку с собой:
за data-gr-layout следит MutationObserver,
и вставленный узел раскладывается наравне с тем, что был в исходном HTML.
Новая раскладка не требует пересборки и выкладки CSS — она уже в
присланной разметке. Механика наблюдателя — на странице
«JS-рантайм», поведение под React и Vue —
«Griffincss в React и Vue».
Детей вы не контролируете
Раскладка целиком живёт на родителе: детям не нужно ни классов, ни знания
о ней. Это решает случай, когда разметку детей выдаёт не ваш код — вывод
виджета CMS, сторонний include, цикл по неизвестному числу элементов.
В чистом CSS то же самое пишется цепочкой :nth-child(),
своей под каждую раскладку, и переписывается руками при первой правке.
Механика — выше, в разделе
«Авто-присвоение grid-area».
Одна раскладка на многих контейнерах
Правило выходит однократно на уникальный набор строк, а класс — хеш от этого набора. Пятьсот контейнеров с одной раскладкой дают одно правило CSS; инлайн-стиль дал бы пятьсот копий в разметке.
Детей больше, чем букв: раздача останавливается,
когда свободные имена кончились, и лишние дети остаются без
класса gr-area-*. Правило скрытия бьёт только по тем,
у кого такой класс есть, — значит эти дети не прячутся, а уходят
в автоматическое размещение CSS Grid, то есть в ряд под шаблоном.
Там, где число блоков задаёт редактор, а не вы, это тихий неверный
ответ вместо громкого отказа: считайте буквы на стороне сервера
или выбирайте раскладку по числу блоков.
Когда лучше не надо
- Раскладка фиксирована. Каркас, написанный один раз, —
это
grid-template-areasили миксин. Атрибут здесь добавляет посредника и зависимость от JS, не давая взамен ничего. - Треки неравные. Строка описывает доли, а не ширины:
280px 1frею не выражается и задаётся отдельно, через--gr-l-cols. Это штатный путь, но если вся суть раскладки именно в неравных дорожках, строка почти ничего не сокращает. - Сетка от количества, а не от схемы. Галерея из N
карточек — это
auto-fitиminmax()со страницы «Grid-хелперы». Строка описывает схему, а не «столько, сколько поместится». - Именованные линии, размеры рядов, masonry. За
пределами языка: ряды —
auto, пока не задан--gr-l-rows. - Контент выше сгиба, критичный без JS. Раскладки из атрибута без выполненного JS нет вовсе — берите миксин, он даёт тот же CSS статикой.
Примеры использования
Строка раскладки сильнее всего там, где флексом пришлось бы городить вложенные обёртки: каркасы страниц, зигзаги, журнальные сетки. Три рецепта ниже — классика, записанная одним атрибутом на контейнер.
Holy grail: каркас с боковыми колонками
Классика документаций и порталов: шапка, навигация слева, контент,
оглавление справа, футер. Вся геометрия — одна строка
hdr6-nav1main4toc1-ftr6. Мобильная версия ещё короче:
в базовом hdr1-main1-ftr1 областей nav
и toc просто нет — и рантайм скрывает эти элементы сам,
без единого .gr-hidden: скрытие отсутствующих областей
встроено в правила раскладки.
<div data-gr-layout="hdr1-main1-ftr1"
data-gr-layout-lg="hdr6-nav1main4toc1-ftr6">
<header class="gr-area-hdr">Шапка</header>
<nav class="gr-area-nav">Навигация</nav>
<main class="gr-area-main">Контент</main>
<aside class="gr-area-toc">Оглавление</aside>
<footer class="gr-area-ftr">Футер</footer>
</div>
Split-секции с зигзагом
Лендинговый «зигзаг» текст-картинка: у первой секции раскладка
txt1img1, у следующей — img1txt1. Сторона
меняется перестановкой букв в строке — не нужны ни order-классы,
ни зеркальная разметка. На мобильном обе секции складываются
в одинаковый столбик txt1-img1: текст всегда сверху,
какой бы ни была десктопная сторона.
<section data-gr-layout="txt1-img1" data-gr-layout-md="txt1img1">
<div class="gr-area-txt">Текст</div>
<div class="gr-area-img">Картинка</div>
</section>
<section data-gr-layout="txt1-img1" data-gr-layout-md="img1txt1">
<div class="gr-area-txt">Текст</div>
<div class="gr-area-img">Картинка</div>
</section>
Быстрый старт
Текст слева, картинка справа.
Гибкие темы
А здесь стороны поменялись — img1txt1.
Бенто-сетка дашборда
Журнальная раскладка из клеток разного размера: три KPI-плитки в ряд,
под ними широкий график на два ряда и колонка списка рядом. Прямоугольник
графика описывается повторением области в соседних рядах —
big2list1-big2list1, — а на мобильном всё это одной базовой
строкой складывается в стопку.
<div data-gr-layout="a1-b1-c1-big1-list1"
data-gr-layout-md="a1b1c1-big2list1-big2list1">
<div class="gr-area-a">Выручка</div>
<div class="gr-area-b">Заказы</div>
<div class="gr-area-c">Конверсия</div>
<div class="gr-area-big">График</div>
<div class="gr-area-list">Последние события</div>
</div>
a1b1c1-big2list1-big2list11,2 млн ₽
318
3,4 %