Files
site-portfolio/public/js/modules/experience/expand.js
T

129 lines
3.9 KiB
JavaScript

import { prefersReducedMotion } from "../shared/media.js";
export function initExperienceExpand(masonry) {
if (!masonry) return;
const btn = document.querySelector("[data-exp-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(".exp-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(".exp-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 setExpanded(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";
setExpanded(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(".exp-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();
}