fix: доработки слайдера и просмотрщика
- case-media-slider: обрамление+hover как у карточек, стрелки только при наведении (fade-in), группа пред/след-пилюля в правом нижнем углу, disabled-кнопки видны (не скрываются), убран border-radius у img/figcaption (скругление у viewport), неактивные слайды не скрываются visibility (нет 'дырки' при перелистывании), настраиваемая ширина --wide/--content/--natural - image-load-placeholder: исключён .case-media-slider (нет блика/серого фона) - icon-press: универсальный эффект 'продавливания' иконки при нажатии (.icon-press + :active/.is-pressed), применён к слайдеру и всем кнопкам просмотрщика
This commit is contained in:
1 parent
95b4eaca93
commit
c14bb1f543
6 files changed
+132
-40
No files matched your search
@@ -453,13 +453,15 @@
|
||||
<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>
|
||||
<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" 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" aria-hidden="true"></span>
|
||||
</button>
|
||||
<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>
|
||||
|
||||
+96
-22
@@ -2363,7 +2363,17 @@ html[data-theme="dark"] {
|
||||
.tui-spinner > span:nth-child(1) { opacity: 1; }
|
||||
}
|
||||
|
||||
/* Лоадер изображения в просмотрщике (обёртка вокруг .tui-spinner) */
|
||||
/* Универсальный эффект «продавливания» иконки при нажатии кнопки */
|
||||
.icon-press {
|
||||
transition: transform 0.12s ease;
|
||||
}
|
||||
|
||||
[class*="btn"]:active:not(:disabled) .icon-press,
|
||||
[class*="nav"]:active:not(:disabled) .icon-press,
|
||||
.btn:active:not(:disabled) .icon-press,
|
||||
.case-media-slider__nav.is-pressed:not(:disabled) .icon-press {
|
||||
transform: scale(0.82);
|
||||
}
|
||||
.case-image-viewer__loader {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
@@ -5338,16 +5348,28 @@ body.content-edit-shift-active.content-edit-mode-active .content-edit-fab-stack
|
||||
--slider-dot-size: 0.5rem;
|
||||
--slider-dot-gap: 0.5rem;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
margin: 0.85rem 0 1.2rem;
|
||||
border-radius: var(--slider-radius);
|
||||
}
|
||||
|
||||
.case-media-slider__viewport {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--slider-radius);
|
||||
box-sizing: border-box;
|
||||
background: color-mix(in srgb, var(--surface) 55%, transparent);
|
||||
touch-action: pan-y;
|
||||
cursor: grab;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.case-media-slider:hover .case-media-slider__viewport {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 8px 24px color-mix(in srgb, var(--accent) 18%, transparent);
|
||||
}
|
||||
|
||||
.case-media-slider__viewport:active {
|
||||
@@ -5376,7 +5398,6 @@ body.content-edit-shift-active.content-edit-mode-active .content-edit-fab-stack
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
object-fit: contain;
|
||||
border-radius: var(--slider-radius);
|
||||
}
|
||||
|
||||
/* Подпись под активным слайдом (как у case-media) */
|
||||
@@ -5392,48 +5413,67 @@ body.content-edit-shift-active.content-edit-mode-active .content-edit-fab-stack
|
||||
text-align: center;
|
||||
background: color-mix(in srgb, var(--surface) 78%, transparent);
|
||||
backdrop-filter: blur(6px);
|
||||
border-bottom-left-radius: var(--slider-radius);
|
||||
border-bottom-right-radius: var(--slider-radius);
|
||||
}
|
||||
|
||||
/* Стрелки */
|
||||
.case-media-slider__nav {
|
||||
/* Группа кнопок пред/след — слитая «пилюля» в нижнем правом углу */
|
||||
.case-media-slider__nav-group {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: var(--slider-ctrl-inset);
|
||||
bottom: var(--slider-ctrl-inset);
|
||||
z-index: 3;
|
||||
display: inline-flex;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity 0.18s ease;
|
||||
}
|
||||
|
||||
.case-media-slider:hover .case-media-slider__nav-group,
|
||||
.case-media-slider__nav-group:focus-within {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.case-media-slider__nav {
|
||||
width: var(--slider-ctrl-size);
|
||||
height: var(--slider-ctrl-size);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 50%;
|
||||
border-radius: 0;
|
||||
background: color-mix(in srgb, var(--surface) 86%, transparent);
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
z-index: 3;
|
||||
transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease,
|
||||
opacity 0.18s ease;
|
||||
transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease;
|
||||
}
|
||||
|
||||
.case-media-slider__nav--prev {
|
||||
left: var(--slider-ctrl-inset);
|
||||
/* Соседняя кнопка перекрывает разделительный бордер */
|
||||
.case-media-slider__nav + .case-media-slider__nav {
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.case-media-slider__nav--next {
|
||||
right: var(--slider-ctrl-inset);
|
||||
/* Скругления только на крайних кнопках группы */
|
||||
.case-media-slider__nav:first-child {
|
||||
border-radius: 999px 0 0 999px;
|
||||
}
|
||||
|
||||
.case-media-slider__nav:last-child {
|
||||
border-radius: 0 999px 999px 0;
|
||||
}
|
||||
|
||||
.case-media-slider__nav:hover:not(:disabled),
|
||||
.case-media-slider__nav:focus-visible:not(:disabled) {
|
||||
color: var(--accent-text);
|
||||
border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
|
||||
background: var(--accent-soft);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.case-media-slider__nav:disabled {
|
||||
opacity: 0.35;
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.case-media-slider__nav-ico {
|
||||
@@ -5447,8 +5487,11 @@ body.content-edit-shift-active.content-edit-mode-active .content-edit-fab-stack
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
-webkit-mask-position: center;
|
||||
transition: transform 0.12s ease;
|
||||
}
|
||||
|
||||
/* Нажатие: иконка слегка «продавливается» внутрь (общий .icon-press) */
|
||||
|
||||
.case-media-slider__nav-ico--left {
|
||||
mask-image: url("../assets/shared/icons/shevron-left.svg");
|
||||
-webkit-mask-image: url("../assets/shared/icons/shevron-left.svg");
|
||||
@@ -5463,9 +5506,11 @@ body.content-edit-shift-active.content-edit-mode-active .content-edit-fab-stack
|
||||
.case-media-slider__dots {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--slider-dot-gap);
|
||||
margin-top: 0.9rem;
|
||||
padding: 0.2rem 0;
|
||||
}
|
||||
|
||||
.case-media-slider__dot {
|
||||
@@ -5486,12 +5531,41 @@ 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.25);
|
||||
transform: scale(1.35);
|
||||
}
|
||||
|
||||
/* Неактивные слайды скрыты от скринридеров и не ловят клики по viewer */
|
||||
/* Неактивные слайды: скрыты от скринридеров (aria-hidden в JS), но остаются видимыми,
|
||||
чтобы при сдвиге трека не было «дырки» от мгновенно исчезнувшего кадра. */
|
||||
.case-media-slider__slide:not(.is-active) {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Настраиваемая ширина — как у статичных .case-media (--wide / --content / --natural).
|
||||
Кнопки пред/след внутри viewport, поэтому не участвуют в расчёте ширины. */
|
||||
.case-media-slider.case-media--content,
|
||||
.case-media-slider.case-media--wide {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.case-media-slider.case-media--content .case-media-slider__viewport,
|
||||
.case-media-slider.case-media--wide .case-media-slider__viewport {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.case-media-slider.case-media--natural {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.case-media-slider.case-media--natural .case-media-slider__viewport {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.case-media-slider.case-media--natural .case-media-slider__slide img {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
@@ -17,10 +17,10 @@ export function initCaseImageViewer() {
|
||||
<div class="case-image-viewer__toolbar glass-neutral" role="toolbar" aria-label="Просмотр изображения">
|
||||
<div class="case-image-viewer__nav-group" role="group" aria-label="Навигация по изображениям">
|
||||
<button type="button" class="btn btn--secondary btn--flat case-image-viewer__btn case-image-viewer__btn--nav" data-imgv-prev aria-label="Предыдущее изображение" hidden>
|
||||
<span class="case-image-viewer__btn-icon" style="--imgv-icon:url('../assets/shared/icons/shevron-left.svg')" aria-hidden="true"></span>
|
||||
<span class="case-image-viewer__btn-icon icon-press" style="--imgv-icon:url('../assets/shared/icons/shevron-left.svg')" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn--secondary btn--flat case-image-viewer__btn case-image-viewer__btn--nav" data-imgv-next aria-label="Следующее изображение" hidden>
|
||||
<span class="case-image-viewer__btn-icon" style="--imgv-icon:url('../assets/shared/icons/shevron-right.svg')" aria-hidden="true"></span>
|
||||
<span class="case-image-viewer__btn-icon icon-press" style="--imgv-icon:url('../assets/shared/icons/shevron-right.svg')" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="case-image-viewer__dots" data-imgv-dots role="tablist" aria-label="Выбор изображения" hidden></div>
|
||||
@@ -29,13 +29,13 @@ export function initCaseImageViewer() {
|
||||
<span class="case-image-viewer__zoom" data-imgv-zoom-label>100%</span>
|
||||
</div>
|
||||
<button type="button" class="btn btn--secondary btn--flat case-image-viewer__btn" data-imgv-fit aria-label="Уместить на экран">
|
||||
<span class="case-image-viewer__btn-icon" style="--imgv-icon:url('../assets/shared/icons/zoom_to_fit.svg')" aria-hidden="true"></span>
|
||||
<span class="case-image-viewer__btn-icon icon-press" style="--imgv-icon:url('../assets/shared/icons/zoom_to_fit.svg')" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn--secondary btn--flat case-image-viewer__btn" data-imgv-original aria-label="Оригинальный размер">
|
||||
<span class="case-image-viewer__btn-icon" style="--imgv-icon:url('../assets/shared/icons/original_size.svg')" aria-hidden="true"></span>
|
||||
<span class="case-image-viewer__btn-icon icon-press" style="--imgv-icon:url('../assets/shared/icons/original_size.svg')" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn--secondary btn--flat case-image-viewer__btn" data-imgv-close aria-label="Закрыть просмотр">
|
||||
<span class="case-image-viewer__btn-icon" style="--imgv-icon:url('../assets/shared/icons/close.svg')" aria-hidden="true"></span>
|
||||
<span class="case-image-viewer__btn-icon icon-press" style="--imgv-icon:url('../assets/shared/icons/close.svg')" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="case-image-viewer__stage" data-imgv-stage tabindex="0">
|
||||
|
||||
@@ -42,6 +42,20 @@ export function initCaseMediaSlider() {
|
||||
const track = root.querySelector("[data-slider-track]");
|
||||
const prevBtn = root.querySelector("[data-slider-prev]");
|
||||
const nextBtn = root.querySelector("[data-slider-next]");
|
||||
const navBtns = [prevBtn, nextBtn].filter(Boolean);
|
||||
|
||||
// Имитация :active для иконки (swipe-обработчик viewport сбрасывает нативный :active)
|
||||
navBtns.forEach((btn) => {
|
||||
const press = () => btn.classList.add("is-pressed");
|
||||
const release = () => btn.classList.remove("is-pressed");
|
||||
btn.addEventListener("pointerdown", press);
|
||||
btn.addEventListener("mousedown", press);
|
||||
btn.addEventListener("pointerup", release);
|
||||
btn.addEventListener("mouseup", release);
|
||||
btn.addEventListener("pointerleave", release);
|
||||
btn.addEventListener("mouseleave", release);
|
||||
btn.addEventListener("pointercancel", release);
|
||||
});
|
||||
const dotsWrap = root.querySelector("[data-slider-dots]");
|
||||
if (!viewport || !track) return;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { isTinyPlaceholderSrc } from "../shared/image-src.js";
|
||||
const HOST_SELECTOR =
|
||||
".case-media, .case-media-gallery__item, .case-link-card__media, .case-media-tall__clip, .mp-card__media, .portfolio-card__cover, figure";
|
||||
|
||||
const EXCLUDE_ANCESTOR = ".contact-card__icon, .cookie-notice, .case-image-viewer, [data-no-img-placeholder]";
|
||||
const EXCLUDE_ANCESTOR = ".contact-card__icon, .cookie-notice, .case-image-viewer, .case-media-slider, [data-no-img-placeholder]";
|
||||
|
||||
function shouldWatch(img) {
|
||||
if (!(img instanceof HTMLImageElement)) return false;
|
||||
|
||||
@@ -264,13 +264,15 @@
|
||||
<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>
|
||||
<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" 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" aria-hidden="true"></span>
|
||||
</button>
|
||||
<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>
|
||||
|
||||
Reference in new issue
Block a user