feat: варианты анимации слайдера (slide/fade/none), пилюли-точки, blur-фон просмотрщика, фикс открытия в natural-слайдерах
No files matched your search
@@ -154,6 +154,14 @@
|
||||
- ленивая подгрузка соседних кадров: у `<img>` можно задать `data-src` (временный `src` — прозрачный GIF), скрипт подставит при подходе, как у галереи; пока есть `data-src`, клик по viewer не открывается;
|
||||
- клик по активному кадру открывает общий viewer кейса (`data-image-viewer`).
|
||||
|
||||
**Ширина слайдера подстраивается под ширину картинок** (высота — автоматически по контенту). Модификаторы как у статичных `.case-media`:
|
||||
|
||||
- базовый (без модификатора) / `.case-media--content` — ширина = `--slider-natural-w` (по умолчанию 100% колонки контента). Задайте на `<figure>` inline-стиль `--slider-natural-w: <ширина картинки>px`, чтобы слайдер точно встал в размер кадров и не превышал его; `max-width` ограничен вьюпортом, поэтому на узких экранах сжимается;
|
||||
- `.case-media--wide` — слайдер выходит за колонку контента (как одиночные wide-картинки), центрируется по вьюпорту;
|
||||
- `.case-media--natural` — как базовый, но `max-width` по умолчанию `600px` (переопределите `--slider-natural-w`).
|
||||
|
||||
Кнопки «пред/след» и точки находятся **внутри** `.case-media-slider__viewport`, поэтому не участвуют в расчёте ширины блока.
|
||||
|
||||
**Разметка** (число `data-slider-dot` должно равняться числу `.case-media-slider__slide`; `aria-label` точек — «Перейти к изображению N»):
|
||||
|
||||
```html
|
||||
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
@@ -470,6 +470,196 @@
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<p class="section__prose">
|
||||
Тот же слайдер, но с модификатором <code>case-media--wide</code> — выходит за колонку
|
||||
контента, как одиночные широкие картинки. Кнопки пред/след и точки ведут себя так же.
|
||||
</p>
|
||||
<figure class="case-media case-media-slider case-media--wide" data-case-media-slider>
|
||||
<div class="case-media-slider__viewport" data-slider-viewport>
|
||||
<div class="case-media-slider__track" data-slider-track>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-wide-1.webp" alt="Слайдер wide, кадр 1" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-wide-2.webp" alt="Слайдер wide, кадр 2" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-wide-3.webp" alt="Слайдер wide, кадр 3" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-wide-4.webp" alt="Слайдер wide, кадр 4" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-wide-5.webp" alt="Слайдер wide, кадр 5" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-wide-6.webp" alt="Слайдер wide, кадр 6" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-wide-7.webp" alt="Слайдер wide, кадр 7" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__nav-group">
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--prev" data-slider-prev aria-label="Предыдущее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--left icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--next" data-slider-next aria-label="Следующее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--right icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__dots" data-slider-dots role="tablist" aria-label="Выбор изображения">
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 1"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 2"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 3"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 4"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 5"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 6"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 7"></button>
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<p class="section__prose">
|
||||
Слайдер с горизонтальными (широкими) кадрами одного размера — все картинки
|
||||
<code>1024×800</code>.
|
||||
</p>
|
||||
<figure class="case-media case-media-slider case-media--natural" data-case-media-slider style="--slider-natural-w: 1024px">
|
||||
<div class="case-media-slider__viewport" data-slider-viewport>
|
||||
<div class="case-media-slider__track" data-slider-track>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-wide-1.webp" alt="Широкий слайдер, кадр 1" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-wide-2.webp" alt="Широкий слайдер, кадр 2" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-wide-3.webp" alt="Широкий слайдер, кадр 3" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-wide-4.webp" alt="Широкий слайдер, кадр 4" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-wide-5.webp" alt="Широкий слайдер, кадр 5" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-wide-6.webp" alt="Широкий слайдер, кадр 6" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-wide-7.webp" alt="Широкий слайдер, кадр 7" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__nav-group">
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--prev" data-slider-prev aria-label="Предыдущее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--left icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--next" data-slider-next aria-label="Следующее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--right icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__dots" data-slider-dots role="tablist" aria-label="Выбор изображения">
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 1"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 2"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 3"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 4"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 5"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 6"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 7"></button>
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<p class="section__prose">
|
||||
Тот же слайдер, но с вертикальными кадрами одного размера — все картинки
|
||||
<code>600×800</code>.
|
||||
</p>
|
||||
<figure class="case-media case-media-slider case-media--natural" data-case-media-slider style="--slider-natural-w: 600px">
|
||||
<div class="case-media-slider__viewport" data-slider-viewport>
|
||||
<div class="case-media-slider__track" data-slider-track>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-portrait-1.webp" alt="Вертикальный слайдер, кадр 1" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-portrait-2.webp" alt="Вертикальный слайдер, кадр 2" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-portrait-3.webp" alt="Вертикальный слайдер, кадр 3" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-portrait-4.webp" alt="Вертикальный слайдер, кадр 4" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-portrait-5.webp" alt="Вертикальный слайдер, кадр 5" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-portrait-6.webp" alt="Вертикальный слайдер, кадр 6" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-portrait-7.webp" alt="Вертикальный слайдер, кадр 7" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__nav-group">
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--prev" data-slider-prev aria-label="Предыдущее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--left icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--next" data-slider-next aria-label="Следующее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--right icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__dots" data-slider-dots role="tablist" aria-label="Выбор изображения">
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 1"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 2"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 3"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 4"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 5"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 6"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 7"></button>
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<p class="section__prose">
|
||||
Отдельный пример: переключатель режима анимации смены кадров —
|
||||
<code>slide</code> (сдвиг), <code>fade</code> (кроссфейд) и <code>none</code> (без перехода).
|
||||
Режим задаётся атрибутом <code>data-slider-anim</code> на фигуре.
|
||||
</p>
|
||||
<figure class="case-media case-media-slider" data-case-media-slider data-slider-anim="slide">
|
||||
<div class="case-media-slider__anim-switch" data-slider-anim-switch role="group" aria-label="Режим анимации слайдера">
|
||||
<button type="button" data-mode="slide" aria-pressed="true">Слайд</button>
|
||||
<button type="button" data-mode="fade" aria-pressed="false">Fade</button>
|
||||
<button type="button" data-mode="none" aria-pressed="false">None</button>
|
||||
</div>
|
||||
<div class="case-media-slider__viewport" data-slider-viewport>
|
||||
<div class="case-media-slider__track" data-slider-track>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-demo-1.webp" alt="Пример анимации, кадр 1" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-demo-2.webp" alt="Пример анимации, кадр 2" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-demo-3.webp" alt="Пример анимации, кадр 3" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="../../assets/template-elements-demo/slider-demo-4.webp" alt="Пример анимации, кадр 4" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__nav-group">
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--prev" data-slider-prev aria-label="Предыдущее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--left icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--next" data-slider-next aria-label="Следующее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--right icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__dots" data-slider-dots role="tablist" aria-label="Выбор изображения">
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 1"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 2"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 3"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 4"></button>
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<h3>Callout: важный абзац с эмоджи</h3>
|
||||
<aside class="case-callout" data-callout>
|
||||
<span class="case-callout__emoji case-callout__emoji--pulse" aria-hidden="true">📌</span>
|
||||
|
||||
@@ -2106,9 +2106,9 @@ img[data-image-viewer]:not([data-src]):focus-visible {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1400;
|
||||
background: color-mix(in srgb, var(--bg) 58%, black);
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
background: color-mix(in srgb, var(--bg) 72%, transparent);
|
||||
backdrop-filter: blur(22px) saturate(130%);
|
||||
-webkit-backdrop-filter: blur(22px) saturate(130%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.18s ease;
|
||||
}
|
||||
@@ -2369,8 +2369,8 @@ html[data-theme="dark"] {
|
||||
}
|
||||
|
||||
[class*="btn"]:active:not(:disabled) .icon-press,
|
||||
[class*="nav"]:active:not(:disabled) .icon-press,
|
||||
.btn:active:not(:disabled) .icon-press,
|
||||
.case-image-viewer__btn--nav:active:not(:disabled) .icon-press,
|
||||
.case-media-slider__nav.is-pressed:not(:disabled) .icon-press {
|
||||
transform: scale(0.82);
|
||||
}
|
||||
@@ -2471,14 +2471,14 @@ html[data-theme="dark"] {
|
||||
}
|
||||
|
||||
.case-image-viewer__dot {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
width: 0.375rem;
|
||||
height: 0.375rem;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: color-mix(in srgb, var(--text-muted) 40%, transparent);
|
||||
cursor: pointer;
|
||||
transition: background 0.18s ease, transform 0.18s ease;
|
||||
transition: background 0.22s ease, width 0.28s cubic-bezier(0.22, 0.61, 0.36, 1), border-radius 0.28s ease;
|
||||
}
|
||||
|
||||
.case-image-viewer__dot:hover,
|
||||
@@ -2488,7 +2488,8 @@ html[data-theme="dark"] {
|
||||
|
||||
.case-image-viewer__dot.is-active {
|
||||
background: var(--accent);
|
||||
transform: scale(1.25);
|
||||
width: calc(0.375rem * 2.6);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
@@ -5345,8 +5346,8 @@ body.content-edit-shift-active.content-edit-mode-active .content-edit-fab-stack
|
||||
--slider-radius: var(--radius-card);
|
||||
--slider-ctrl-size: 2.5rem;
|
||||
--slider-ctrl-inset: 0.75rem;
|
||||
--slider-dot-size: 0.5rem;
|
||||
--slider-dot-gap: 0.5rem;
|
||||
--slider-dot-size: 0.375rem;
|
||||
--slider-dot-gap: 0.375rem;
|
||||
position: relative;
|
||||
margin: 0.85rem 0 1.2rem;
|
||||
border-radius: var(--slider-radius);
|
||||
@@ -5521,7 +5522,7 @@ body.content-edit-shift-active.content-edit-mode-active .content-edit-fab-stack
|
||||
border-radius: 50%;
|
||||
background: color-mix(in srgb, var(--text-muted) 40%, transparent);
|
||||
cursor: pointer;
|
||||
transition: background 0.18s ease, transform 0.18s ease;
|
||||
transition: background 0.22s ease, width 0.28s cubic-bezier(0.22, 0.61, 0.36, 1), border-radius 0.28s ease;
|
||||
}
|
||||
|
||||
.case-media-slider__dot:hover,
|
||||
@@ -5531,7 +5532,8 @@ body.content-edit-shift-active.content-edit-mode-active .content-edit-fab-stack
|
||||
|
||||
.case-media-slider__dot.is-active {
|
||||
background: var(--accent);
|
||||
transform: scale(1.35);
|
||||
width: calc(var(--slider-dot-size) * 2.6);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
/* Неактивные слайды: скрыты от скринридеров (aria-hidden в JS), но остаются видимыми,
|
||||
@@ -5541,30 +5543,52 @@ body.content-edit-shift-active.content-edit-mode-active .content-edit-fab-stack
|
||||
}
|
||||
|
||||
/* Настраиваемая ширина — как у статичных .case-media (--wide / --content / --natural).
|
||||
Кнопки пред/след внутри viewport, поэтому не участвуют в расчёте ширины. */
|
||||
.case-media-slider.case-media--content,
|
||||
.case-media-slider.case-media--wide {
|
||||
Кнопки пред/след внутри viewport, поэтому не участвуют в расчёте ширины.
|
||||
Высота — автоматически по контенту. */
|
||||
|
||||
/* Базовый / --content: встроен в колонку контента, не шире её */
|
||||
.case-media-slider,
|
||||
.case-media-slider.case-media--content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.case-media-slider.case-media--content .case-media-slider__viewport,
|
||||
.case-media-slider.case-media--wide .case-media-slider__viewport {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
/* --wide: как одиночные wide-картинки страницы — выходит за колонку, центр по вьюпорту */
|
||||
.case-media-slider.case-media--wide {
|
||||
width: min(calc(100vw - (max(var(--space), env(safe-area-inset-left)) + max(var(--space), env(safe-area-inset-right)))), calc(100% + 8rem));
|
||||
max-width: none;
|
||||
margin-left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
/* --natural / auto: ширина по картинке (--slider-natural-w или авто из JS), не больше ширины экрана */
|
||||
.case-media-slider.case-media--natural {
|
||||
display: block;
|
||||
width: min(var(--slider-natural-w, 100%), 100vw);
|
||||
max-width: 100vw;
|
||||
margin-left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.case-media-slider .case-media-slider__viewport,
|
||||
.case-media-slider.case-media--content .case-media-slider__viewport,
|
||||
.case-media-slider.case-media--wide .case-media-slider__viewport,
|
||||
.case-media-slider.case-media--natural .case-media-slider__viewport {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.case-media-slider .case-media-slider__slide img,
|
||||
.case-media-slider.case-media--content .case-media-slider__slide img,
|
||||
.case-media-slider.case-media--wide .case-media-slider__slide img,
|
||||
.case-media-slider.case-media--natural .case-media-slider__slide img {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
max-height: none;
|
||||
height: auto;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
@@ -5573,3 +5597,64 @@ body.content-edit-shift-active.content-edit-mode-active .content-edit-fab-stack
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Режимы анимации переключения кадров.
|
||||
slide — сдвиг трека (по умолчанию); fade — кроссфейд активного слайда;
|
||||
none — мгновенная смена без переходов. */
|
||||
.case-media-slider--anim-fade .case-media-slider__track {
|
||||
display: block;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.case-media-slider--anim-fade .case-media-slider__slide {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.45s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.case-media-slider--anim-fade .case-media-slider__slide.is-active {
|
||||
position: relative;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.case-media-slider--anim-none .case-media-slider__track {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.case-media-slider--anim-none .case-media-slider__slide {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
/* Переключатель режима анимации (для демо-примера) */
|
||||
.case-media-slider__anim-switch {
|
||||
display: inline-flex;
|
||||
gap: 0.4rem;
|
||||
margin: 0.4rem 0 0.2rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.case-media-slider__anim-switch button {
|
||||
font: inherit;
|
||||
font-size: 0.8rem;
|
||||
padding: 0.25rem 0.7rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
|
||||
}
|
||||
|
||||
.case-media-slider__anim-switch button:hover {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.case-media-slider__anim-switch button[aria-pressed="true"] {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
@@ -324,7 +324,7 @@ export function initCaseImageViewer() {
|
||||
const t = e.target;
|
||||
if (!(t instanceof Element)) return;
|
||||
const img = t.closest("img[data-image-viewer]");
|
||||
if (!img || img.closest(".case-media--natural")) return;
|
||||
if (!img) return;
|
||||
if (img.hasAttribute("data-src")) return;
|
||||
const src = img.currentSrc || img.getAttribute("src") || "";
|
||||
if (!src || isViewerThumbnailPlaceholder(src)) return;
|
||||
|
||||
@@ -59,6 +59,22 @@ export function initCaseMediaSlider() {
|
||||
const dotsWrap = root.querySelector("[data-slider-dots]");
|
||||
if (!viewport || !track) return;
|
||||
|
||||
// Авто-подстройка ширины под первую картинку — только для --natural (auto) слайдеров,
|
||||
// и только если --slider-natural-w не задана вручную. Базовый/wide управляются CSS.
|
||||
const firstImg = root.querySelector(".case-media-slider__slide img");
|
||||
const applyAutoWidth = (img) => {
|
||||
if (root.style.getPropertyValue("--slider-natural-w")) return;
|
||||
const nw = img.naturalWidth || 0;
|
||||
if (nw > 0) root.style.setProperty("--slider-natural-w", `${nw}px`);
|
||||
};
|
||||
if (root.classList.contains("case-media--natural") && firstImg) {
|
||||
if (firstImg.complete && firstImg.naturalWidth > 0) {
|
||||
applyAutoWidth(firstImg);
|
||||
} else {
|
||||
firstImg.addEventListener("load", () => applyAutoWidth(firstImg), { once: true });
|
||||
}
|
||||
}
|
||||
|
||||
const slides = Array.from(track.querySelectorAll(":scope > .case-media-slider__slide"));
|
||||
const dots = dotsWrap
|
||||
? Array.from(dotsWrap.querySelectorAll(":scope > [data-slider-dot]"))
|
||||
@@ -72,11 +88,33 @@ export function initCaseMediaSlider() {
|
||||
"(prefers-reduced-motion: reduce)"
|
||||
).matches;
|
||||
|
||||
// Режим анимации переключения: slide (по умолчанию) / fade / none
|
||||
let mode = root.getAttribute("data-slider-anim") || "slide";
|
||||
function applyMode(m) {
|
||||
mode = m;
|
||||
root.classList.remove(
|
||||
"case-media-slider--anim-slide",
|
||||
"case-media-slider--anim-fade",
|
||||
"case-media-slider--anim-none"
|
||||
);
|
||||
root.classList.add(`case-media-slider--anim-${m}`);
|
||||
if (m === "fade") {
|
||||
track.style.transform = "none";
|
||||
} else {
|
||||
track.style.transform = `translateX(${-100 * current}%)`;
|
||||
}
|
||||
}
|
||||
applyMode(mode);
|
||||
|
||||
function goTo(index, behavior) {
|
||||
const next = clampIndex(index, count);
|
||||
current = next;
|
||||
const offset = -100 * next;
|
||||
track.style.transform = `translateX(${offset}%)`;
|
||||
if (mode !== "fade") {
|
||||
const offset = -100 * next;
|
||||
track.style.transform = `translateX(${offset}%)`;
|
||||
} else {
|
||||
track.style.transform = "none";
|
||||
}
|
||||
track.style.transition = reduceMotion || behavior === "auto"
|
||||
? "none"
|
||||
: "";
|
||||
@@ -151,5 +189,21 @@ export function initCaseMediaSlider() {
|
||||
s.classList.toggle("is-active", i === 0);
|
||||
});
|
||||
goTo(0, "auto");
|
||||
|
||||
// UI-переключатель режима анимации (для демо-примера), если есть в разметке.
|
||||
const animSwitch = root.querySelector("[data-slider-anim-switch]");
|
||||
if (animSwitch) {
|
||||
const btns = Array.from(animSwitch.querySelectorAll("button"));
|
||||
const sync = () => btns.forEach((b) => {
|
||||
b.setAttribute("aria-pressed", String(b.dataset.mode === mode));
|
||||
});
|
||||
sync();
|
||||
btns.forEach((b) => {
|
||||
b.addEventListener("click", () => {
|
||||
applyMode(b.dataset.mode || "slide");
|
||||
sync();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
export function initCaseNaturalImagesHalfSize() {
|
||||
const images = Array.from(document.querySelectorAll(".case-media--natural img"));
|
||||
const images = Array.from(
|
||||
document.querySelectorAll(".case-media--natural img")
|
||||
).filter((img) => !img.closest(".case-media-slider"));
|
||||
if (!images.length) return;
|
||||
|
||||
function applyHalfSize(img) {
|
||||
|
||||
@@ -281,6 +281,196 @@
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<p class="section__prose">
|
||||
Тот же слайдер, но с модификатором <code>case-media--wide</code> — выходит за колонку
|
||||
контента, как одиночные широкие картинки. Кнопки пред/след и точки ведут себя так же.
|
||||
</p>
|
||||
<figure class="case-media case-media-slider case-media--wide" data-case-media-slider>
|
||||
<div class="case-media-slider__viewport" data-slider-viewport>
|
||||
<div class="case-media-slider__track" data-slider-track>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-wide-1.webp" alt="Слайдер wide, кадр 1" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-wide-2.webp" alt="Слайдер wide, кадр 2" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-wide-3.webp" alt="Слайдер wide, кадр 3" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-wide-4.webp" alt="Слайдер wide, кадр 4" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-wide-5.webp" alt="Слайдер wide, кадр 5" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-wide-6.webp" alt="Слайдер wide, кадр 6" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-wide-7.webp" alt="Слайдер wide, кадр 7" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__nav-group">
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--prev" data-slider-prev aria-label="Предыдущее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--left icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--next" data-slider-next aria-label="Следующее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--right icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__dots" data-slider-dots role="tablist" aria-label="Выбор изображения">
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 1"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 2"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 3"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 4"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 5"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 6"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 7"></button>
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<p class="section__prose">
|
||||
Слайдер с горизонтальными (широкими) кадрами одного размера — все картинки
|
||||
<code>1024×800</code>.
|
||||
</p>
|
||||
<figure class="case-media case-media-slider case-media--natural" data-case-media-slider style="--slider-natural-w: 1024px">
|
||||
<div class="case-media-slider__viewport" data-slider-viewport>
|
||||
<div class="case-media-slider__track" data-slider-track>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-wide-1.webp" alt="Широкий слайдер, кадр 1" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-wide-2.webp" alt="Широкий слайдер, кадр 2" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-wide-3.webp" alt="Широкий слайдер, кадр 3" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-wide-4.webp" alt="Широкий слайдер, кадр 4" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-wide-5.webp" alt="Широкий слайдер, кадр 5" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-wide-6.webp" alt="Широкий слайдер, кадр 6" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-wide-7.webp" alt="Широкий слайдер, кадр 7" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__nav-group">
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--prev" data-slider-prev aria-label="Предыдущее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--left icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--next" data-slider-next aria-label="Следующее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--right icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__dots" data-slider-dots role="tablist" aria-label="Выбор изображения">
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 1"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 2"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 3"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 4"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 5"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 6"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 7"></button>
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<p class="section__prose">
|
||||
Тот же слайдер, но с вертикальными кадрами одного размера — все картинки
|
||||
<code>600×800</code>.
|
||||
</p>
|
||||
<figure class="case-media case-media-slider case-media--natural" data-case-media-slider style="--slider-natural-w: 600px">
|
||||
<div class="case-media-slider__viewport" data-slider-viewport>
|
||||
<div class="case-media-slider__track" data-slider-track>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-portrait-1.webp" alt="Вертикальный слайдер, кадр 1" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-portrait-2.webp" alt="Вертикальный слайдер, кадр 2" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-portrait-3.webp" alt="Вертикальный слайдер, кадр 3" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-portrait-4.webp" alt="Вертикальный слайдер, кадр 4" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-portrait-5.webp" alt="Вертикальный слайдер, кадр 5" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-portrait-6.webp" alt="Вертикальный слайдер, кадр 6" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-portrait-7.webp" alt="Вертикальный слайдер, кадр 7" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__nav-group">
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--prev" data-slider-prev aria-label="Предыдущее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--left icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--next" data-slider-next aria-label="Следующее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--right icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__dots" data-slider-dots role="tablist" aria-label="Выбор изображения">
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 1"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 2"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 3"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 4"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 5"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 6"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 7"></button>
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<p class="section__prose">
|
||||
Отдельный пример: переключатель режима анимации смены кадров —
|
||||
<code>slide</code> (сдвиг), <code>fade</code> (кроссфейд) и <code>none</code> (без перехода).
|
||||
Режим задаётся атрибутом <code>data-slider-anim</code> на фигуре.
|
||||
</p>
|
||||
<figure class="case-media case-media-slider" data-case-media-slider data-slider-anim="slide">
|
||||
<div class="case-media-slider__anim-switch" data-slider-anim-switch role="group" aria-label="Режим анимации слайдера">
|
||||
<button type="button" data-mode="slide" aria-pressed="true">Слайд</button>
|
||||
<button type="button" data-mode="fade" aria-pressed="false">Fade</button>
|
||||
<button type="button" data-mode="none" aria-pressed="false">None</button>
|
||||
</div>
|
||||
<div class="case-media-slider__viewport" data-slider-viewport>
|
||||
<div class="case-media-slider__track" data-slider-track>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-demo-1.webp" alt="Пример анимации, кадр 1" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-demo-2.webp" alt="Пример анимации, кадр 2" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-demo-3.webp" alt="Пример анимации, кадр 3" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
<div class="case-media-slider__slide">
|
||||
<img src="{{assetPrefix}}assets/template-elements-demo/slider-demo-4.webp" alt="Пример анимации, кадр 4" loading="lazy" decoding="async" data-image-viewer />
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__nav-group">
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--prev" data-slider-prev aria-label="Предыдущее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--left icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="case-media-slider__nav case-media-slider__nav--next" data-slider-next aria-label="Следующее изображение">
|
||||
<span class="case-media-slider__nav-ico case-media-slider__nav-ico--right icon-press" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="case-media-slider__dots" data-slider-dots role="tablist" aria-label="Выбор изображения">
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 1"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 2"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 3"></button>
|
||||
<button type="button" class="case-media-slider__dot" data-slider-dot aria-label="Перейти к изображению 4"></button>
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<h3>Callout: важный абзац с эмоджи</h3>
|
||||
<aside class="case-callout" data-callout>
|
||||
<span class="case-callout__emoji case-callout__emoji--pulse" aria-hidden="true">📌</span>
|
||||
|
||||