Files
site-portfolio/public/js/modules/mini-projects/expand.js
T

136 lines
4.3 KiB
JavaScript

import { prefersReducedMotion } from "../shared/media.js";
export function initMiniProjectsExpand(masonry) {
if (!masonry) return;
const controllers = Array.isArray(masonry) ? masonry : [masonry];
controllers.forEach(initMiniProjectsExpandForList);
}
function initMiniProjectsExpandForList(masonry) {
if (!masonry?.list) return;
const scope = masonry.list.closest("section, main") ?? masonry.list.parentElement;
const btn = scope?.querySelector("[data-mp-toggle]");
const extras = masonry.extraItems;
if (!btn || extras.length === 0) return;
const labelExpand = "Показать все";
const labelCollapse = "Свернуть";
const reducedMotion = prefersReducedMotion();
let locked = false;
const wait = (ms) => new Promise((resolve) => window.setTimeout(resolve, ms));
function animateOpen(el, delayMs) {
const card = el.querySelector(".mp-card");
if (!card) return Promise.resolve();
if (reducedMotion) {
el.setAttribute("aria-hidden", "false");
card.style.opacity = "1";
card.style.transform = "translateY(0)";
card.style.pointerEvents = "";
return Promise.resolve();
}
const duration = 280;
el.setAttribute("aria-hidden", "false");
card.style.opacity = "0";
card.style.transform = "translateY(12px)";
card.style.pointerEvents = "";
card.style.willChange = "opacity, transform";
return new Promise((resolve) => {
window.setTimeout(() => {
card.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
card.style.opacity = "1";
card.style.transform = "translateY(0)";
}, delayMs);
window.setTimeout(() => {
card.style.transition = "";
card.style.opacity = "";
card.style.transform = "";
card.style.willChange = "";
resolve();
}, delayMs + duration + 30);
});
}
function animateClose(el, delayMs) {
const card = el.querySelector(".mp-card");
if (!card) return Promise.resolve();
if (reducedMotion) {
el.setAttribute("aria-hidden", "true");
card.style.opacity = "0";
card.style.transform = "translateY(12px)";
card.style.pointerEvents = "none";
return Promise.resolve();
}
const duration = 240;
card.style.opacity = "1";
card.style.transform = "translateY(0)";
card.style.willChange = "opacity, transform";
return new Promise((resolve) => {
window.setTimeout(() => {
card.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
card.style.opacity = "0";
card.style.transform = "translateY(12px)";
}, delayMs);
window.setTimeout(() => {
card.style.transition = "";
card.style.opacity = "";
card.style.transform = "";
card.style.willChange = "";
el.setAttribute("aria-hidden", "true");
card.style.opacity = "0";
card.style.transform = "translateY(12px)";
card.style.pointerEvents = "none";
resolve();
}, delayMs + duration + 30);
});
}
function setExpandedUi(expanded) {
btn.setAttribute("aria-expanded", expanded ? "true" : "false");
btn.textContent = expanded ? labelCollapse : labelExpand;
}
btn.addEventListener("click", async () => {
if (locked) return;
locked = true;
btn.disabled = true;
const next = btn.getAttribute("aria-expanded") !== "true";
setExpandedUi(next);
if (next) {
masonry.setExpanded(true);
const jobs = [];
extras.forEach((el, i) => jobs.push(animateOpen(el, i * 45)));
await Promise.all(jobs);
} else {
const reversed = Array.from(extras).reverse();
const jobs = [];
reversed.forEach((el, i) => jobs.push(animateClose(el, i * 35)));
await Promise.all(jobs);
masonry.setExpanded(false);
await wait(10);
}
masonry.relayout();
btn.disabled = false;
locked = false;
});
extras.forEach((el) => {
const card = el.querySelector(".mp-card");
el.setAttribute("aria-hidden", "true");
if (!card) return;
card.style.opacity = "0";
card.style.transform = "translateY(12px)";
card.style.pointerEvents = "none";
});
masonry.setExpanded(false);
masonry.relayout();
}