Слайды
Дорожка, слайдер, галерея с превью и лайтбокс — семейство прокрутки слоя GriffinJS. Один механизм с разной навигацией и обёрткой; без скрипта у каждого есть рабочая база.
Бейдж нужен griffinjs.js у примера означает, что показанное поведение даёт скрипт. У примеров без бейджа разметка обязана работать без него — это проверяется на полигоне с отключённым скриптом.
Подключение
<link rel="stylesheet" href="griffincss-ui.css">
<link rel="stylesheet" href="griffinjs.css">
<script src="griffinjs.js"></script>
griffinjs.css подключается поверх griffincss-ui.css:
у него нет собственного блока :root, токены берутся из ядра.
Скрипт поднимает виджеты по атрибутам data-gr-* сам; атрибут
data-auto="false" на теге отключает автозапуск — тогда
GriffinJS.start() зовётся вручную.
Дорожка
.gr-track — scroll-snap-контейнер. Каждый прямой потомок —
слайд во всю ширину; свайп, инерция, колесо и RTL — от браузера. Это база
без JS для всего семейства.
| Класс / атрибут | Назначение |
|---|---|
.gr-track | Дорожка: flex, scroll-snap-type: x mandatory, без полосы прокрутки |
.gr-track-2 / -3 / -4 | Слайдов в ряд; суффикс брейкпоинта — .gr-track-1-sm .gr-track-3-md; с буквой c — по ширине ближайшего .gr-cq: .gr-track-3-cmd |
.gr-track-scale | Соседи активного слайда чуть меньше — на --gr-progress; без скрипта — натуральная величина |
.gr-track-stack-c{sm,md,lg,xl} | Столбик, пока ближайший предок с .gr-cq уже порога (640 / 768 / 1024 / 1280 px): подборка в боковой колонке — список, в содержимом — лента. Оконных порогов нет: в узком окне лента остаётся лентой. Слайдеру не подходит — кнопки и точки останутся |
data-gr-track нужен griffinjs.js | Клавиатура (←→ с учётом RTL, Home, End), aria-current и inert на слайдах, --gr-progress, событие griffin:change |
<div class="gr-track gr-track-2-sm gr-track-3-md" tabindex="0" aria-label="Категории">
<div class="gr-card gr-p-4">Первая</div>
<div class="gr-card gr-p-4">Вторая</div>
…
</div>
<aside class="gr-cq-aside">
<div class="gr-track gr-track-stack-csm gr-track-3-cmd" tabindex="0" aria-label="Похожие товары">
<div class="gr-card gr-p-4">…</div>
…
</div>
</aside>
Параметры data-gr-track
| Параметр | По умолчанию | Смысл |
|---|---|---|
loop | false | Бесконечная дорожка: индекс идёт по кругу |
rewind | false | С края — на другой край без клонов |
step | 1 | Шаг next()/prev(): число или page |
index | 0 | Начальный слайд |
keyboard | true | Стрелки, Home, End |
inert | true | Невидимые слайды помечаются inert |
label | Слайд {i} из {n} | Подпись слайда без своей aria-label |
engine | scroll | Движок движения; fade — отдельным модулем |
Слайдер
data-gr-slider на обёртке превращает дорожку в слайдер: кнопки
[data-gr-prev] / [data-gr-next], точки в
[data-gr-dots], автоплей с паузой. Органы управления спрятаны
CSS, пока скрипт не поставил data-gr-state на корень —
бездействующая кнопка хуже отсутствующей.
| Параметр | По умолчанию | Смысл |
|---|---|---|
autoplay | 0 | Миллисекунд между слайдами; 0 — выключено. Уважает prefers-reduced-motion |
loop | false | По кругу; без цикла кнопки у краёв получают aria-disabled |
rewind | false | «Бесконечно» без клонов: с последней страницы — на первую, с первой — на последнюю; кнопки у краёв не блокируются |
step | 1 | Шаг кнопок, точек и свайпа: число слайдов или page — на столько, сколько видно. Снап-точки дорожка ставит только началам страниц (--gr-snap на слайдах), так что палец останавливается там же, куда ведут точки |
pause-on-hover | true | Наведение и фокус держат автоплей; кнопка паузы фиксирует его |
dots | true | Строить точки в [data-gr-dots] — по страницам, не по слайдам |
loop — бесшовный переход через край. У движка scroll
это клоны краёв: по одному видимому слайду с каждой стороны, помеченные
data-gr-clone, aria-hidden и inert;
дорожка доезжает до клона плавно и на остановке мгновенно перескакивает
на настоящий слайд. У fade цикл бесплатен. Если движок цикла
не умеет, loop ведёт себя как rewind.
Шаг и rewind
Пять карточек по две в ряд. Первый слайдер листает step: 2 —
ровно на две карточки, точек три (последняя страница — одна карточка,
выровненная к концу); второй — step: page, то же самое,
но шаг подстраивается под число видимых на любой ширине. У обоих
rewind: с последней страницы кнопка «вперёд» возвращает к первой,
и у краёв ничего не блокируется. Сравните с третьим — без rewind
кнопка у края гаснет.
<div class="gr-slider" data-gr-slider="step: 2; rewind"> <!-- на две карточки -->
<div class="gr-slider" data-gr-slider="step: page; rewind"> <!-- на сколько видно -->
<div class="gr-slider" data-gr-slider="step: page"> <!-- без rewind: у края кнопка недоступна -->
step: 2; rewind нужен griffinjs.jsstep: page; rewind нужен griffinjs.jsstep: page без rewind нужен griffinjs.js
Когда в ряд видно несколько слайдов, крайний достижимый индекс —
count − perView: последний слайд выравнивается к концу дорожки,
и дальше браузер не прокрутит. Точки строятся по страницам от этого предела
(при step: page последняя страница может быть короче шага),
а при смене брейкпоинта — перестраиваются: дорожка шлёт
griffin:layout.
| Часть | Что делает скрипт |
|---|---|
| корень | role="region", aria-roledescription="carousel", data-gr-state: ready / playing / paused |
.gr-track | aria-live="polite"; при автоплее — off |
[data-gr-dots] | кнопки .gr-slider-dot по одной на страницу, активная — aria-current |
[data-gr-pause] | кнопка паузы; создаётся сама, если автоплей включён, а кнопки нет (WCAG 2.2.2). Ход — кольцом из --gr-progress |
<div class="gr-slider" data-gr-slider="autoplay: 4000; loop" aria-label="Акции">
<div class="gr-track">
<div class="frame frame-1">Первый</div>
<div class="frame frame-2">Второй</div>
<div class="frame frame-3">Третий</div>
</div>
<button class="gr-btn gr-btn-icon gr-slider-prev" data-gr-prev aria-label="Назад">‹</button>
<button class="gr-btn gr-btn-icon gr-slider-next" data-gr-next aria-label="Вперёд">›</button>
<div class="gr-slider-dots" data-gr-dots></div>
</div>
Пока дорожка едет по команде — кнопке, точке, клавише, — новые команды
не принимаются и возвращают false: индекс и точки меняются
только вместе с реальным движением, быстрые нажатия не уводят их вперёд.
Свайп пальцем при этом не блокируется, а команды во время жеста
откладываются до его конца. При свайпе индекс и точки переключаются,
как только дорожка прошла середину пути к соседнему слайду (в страничном
режиме — к ближайшему началу страницы), не дожидаясь остановки прокрутки.
Событие и API
Каждое изменение слайда — событие griffin:change на дорожке,
всплывающее до документа: detail.index (виртуальный индекс,
в цикле растёт бесконечно), detail.physical (позиция в DOM),
detail.previous.
const el = document.querySelector('.gr-slider');
const slider = GriffinJS.instance(el, 'slider');
slider.next(); // slider.prev(), slider.goTo(2), slider.pause(), slider.play()
slider.track.index; // текущий индекс
el.addEventListener('griffin:change', (e) => {
console.log(e.detail.physical, e.detail.previous);
});
// Дорожка вручную, без атрибута:
const track = GriffinJS.track(document.querySelector('.gr-track'), { loop: true });
Движок fade
Слайды стопкой, переход прозрачностью. Разметка та же, плюс класс
.gr-track-fade: CSS кладёт слайды в одну ячейку сетки
и связывает opacity с --gr-progress. Движок ничего
не двигает — он публикует позицию, переход делает CSS-transition. Свайп
и тяга — через примитив gesture: пока палец на экране,
прозрачность следует за ним без сглаживания (data-gr-state="dragging"
на дорожке). Цикл — бесплатно, без клонов. Без скрипта показан первый слайд.
<div class="gr-slider" data-gr-slider="engine: fade; loop; autoplay: 4000">
<div class="gr-track gr-track-fade">
<div class="frame frame-1">Первый</div>
…
</div>
<div class="gr-slider-dots" data-gr-dots></div>
</div>
Галерея
data-gr-gallery — слайдер плюс полоса превью
.gr-gallery-thumbs: щелчок по превью ведёт большую дорожку,
активное превью получает aria-current и подъезжает в видимую
часть полосы. Превью — кнопки: клавиатура и фокус у них свои. Параметры —
те же, что у слайдера.
<div class="gr-gallery" data-gr-gallery aria-label="Фотографии товара">
<div class="gr-track">
<div class="frame frame-1 frame-square">1</div>
…
</div>
<div class="gr-track gr-gallery-thumbs">
<button type="button"><div class="frame frame-1 frame-square"></div></button>
…
</div>
</div>
Лайтбокс
data-gr-lightbox на ссылке или кнопке. Значение — имя группы:
элементы с одним значением листаются в одном окне; пустое значение — окно
на один кадр. Окно — <dialog class="gr-modal gr-lightbox">,
строится при открытии и уничтожается при закрытии, поэтому видео
и iframe останавливаются сами. База без JS — обычная ссылка на большое
изображение.
| Атрибут | Смысл |
|---|---|
data-gr-lightbox="группа" | Что открывать; адрес — из href или data-gr-src |
data-gr-caption | Подпись под кадром |
data-gr-alt | Альтернативный текст; по умолчанию — alt вложенной картинки |
data-gr-type | image / video / iframe; по умолчанию угадывается по адресу (mp4/webm, YouTube, Vimeo) |
<a href="data:image/svg+xml,…" data-gr-lightbox="promo" data-gr-caption="Синий">
<div class="frame frame-1 frame-square"></div>
</a>
<a href="data:image/svg+xml,…" data-gr-lightbox="promo" data-gr-caption="Зелёный">…</a>
<!-- из скрипта -->
GriffinJS.lightbox.open([{ src: 'a.jpg', caption: 'Первая' }, { src: 'b.mp4' }], 0);
Прогресс движения
Дорожка публикует --gr-progress: на каждом слайде — 1
на своём месте и 0 на расстоянии в один слайд; на самой дорожке —
долю пройденного пути. Скрипт не пишет ни transform, ни
opacity: эффект целиком в CSS, и запасное значение делает его
безопасным без скрипта.
/* соседи меньше активного — готовый класс .gr-track-scale делает то же */
.my-track > * { scale: calc(0.9 + 0.1 * var(--gr-progress, 1)); }
/* полоса прогресса из --gr-progress дорожки */
.my-bar { inline-size: calc(var(--gr-progress, 0) * 100%); }
Примеры использования
Рецепты ниже собирают семейство прокрутки с компонентами и утилитами библиотеки: баннер витрины, товары в ряд, отзывы, галерея товара, лайтбокс поверх сетки карточек и лента без единой строки скрипта.
Баннер витрины: автоплей, цикл, точки
Кадр во всю ширину, кнопки по краям, точки под кадром. Автоплей
останавливается наведением и фокусом, а кнопка паузы в углу показывает
ход кольцом — WCAG 2.2.2 закрыт без единой своей строки. Слайды —
обычные блоки: внутрь идут .gr-card, кнопки, любая раскладка.
<div class="gr-slider gr-radius-3 gr-overflow-hidden" data-gr-slider="autoplay: 5000; loop" aria-label="Баннеры">
<div class="gr-track">
<div class="frame frame-1 gr-p-6 gr-text-start">
<div><h3 class="gr-m-0">Осенняя коллекция</h3><a class="gr-btn gr-mt-3" href="#">Смотреть</a></div>
</div>
…
</div>
<button class="gr-btn gr-btn-icon gr-slider-prev" data-gr-prev aria-label="Назад">‹</button>
<button class="gr-btn gr-btn-icon gr-slider-next" data-gr-next aria-label="Вперёд">›</button>
<div class="gr-slider-dots" data-gr-dots></div>
</div>
Товары в ряд: один на телефоне, четыре на десктопе
Слайдер и адаптивная дорожка — одна разметка: .gr-track-2-sm
.gr-track-4-lg задаёт число карточек в ряд, step: page
листает сразу на страницу, rewind с последней возвращает
к первой. Точек столько, сколько страниц на текущей ширине — на телефоне
шесть, на десктопе две. Карточки — .gr-card из компонентов,
ничего специального.
<div class="gr-slider" data-gr-slider="step: page; rewind" aria-label="Хиты продаж">
<div class="gr-track gr-track-2-sm gr-track-4-lg">
<div class="gr-card gr-p-4">…</div>
×6
</div>
<button class="gr-btn gr-btn-icon gr-slider-prev" data-gr-prev aria-label="Назад">‹</button>
<button class="gr-btn gr-btn-icon gr-slider-next" data-gr-next aria-label="Вперёд">›</button>
<div class="gr-slider-dots" data-gr-dots></div>
</div>
Отзывы: соседи чуть меньше активного
.gr-track-scale уменьшает соседей на величину, которую дорожка
пишет в --gr-progress. Эффект следует за пальцем при свайпе —
без единого кадра анимации в скрипте. При prefers-reduced-motion
масштаб отключён. Листается по одному отзыву с rewind:
при трёх в ряд из четырёх точек две — по достижимым страницам.
<div class="gr-slider" data-gr-slider="rewind" aria-label="Отзывы">
<div class="gr-track gr-track-scale gr-track-3-md gr-py-4">
<blockquote class="gr-card gr-p-4 gr-m-0">…</blockquote>
…
</div>
<div class="gr-slider-dots" data-gr-dots></div>
</div>
--gr-progress нужен griffinjs.jsГалерея товара: превью снизу, лайтбокс по щелчку
Два виджета в одной разметке: галерея листает кадры и подсвечивает превью,
а data-gr-lightbox на кадрах открывает их во весь экран
той же группой. Оба берут разметку как есть; без скрипта остаются
две дорожки и ссылки на большие изображения.
<div class="gr-gallery" data-gr-gallery aria-label="Фотографии">
<div class="gr-track">
<a href="large-1.jpg" data-gr-lightbox="product" data-gr-caption="Вид спереди"><img …></a>
…
</div>
<div class="gr-track gr-gallery-thumbs">
<button type="button"><img …></button> …
</div>
</div>
Лента без скрипта
Не каждой дорожке нужен слайдер. Лента историй, ряд логотипов, полоса
тегов — .gr-track с шириной слайдов через утилиты
и ничего больше. Это же — «база без JS» любого слайдера на странице,
где griffinjs.js не подключён.
<div class="gr-track gr-gap-sm" tabindex="0" aria-label="Истории">
<a class="gr-w-24 gr-flex-none" href="#">…</a>
…
</div>