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

85 lines
2.8 KiB
JavaScript

export function initExperienceMasonry() {
const list = document.querySelector("#experience-list");
if (!list) return null;
const allItems = Array.from(list.querySelectorAll(".experience-grid__item"));
const extraItems = allItems.filter((el) => el.hasAttribute("data-exp-extra"));
const baseItems = allItems.filter((el) => !el.hasAttribute("data-exp-extra"));
const gap = 16;
let rafId = 0;
let expanded = false;
let fullHeight = 0;
let collapsedHeight = 0;
extraItems.forEach((el) => el.removeAttribute("hidden"));
function columnCountForWidth(w) {
if (w >= 1120) return 3;
if (w >= 760) return 2;
return 1;
}
function performLayout() {
if (!allItems.length) {
list.style.height = "0px";
return;
}
const listWidth = list.clientWidth;
// Колонки считаем по фактической ширине списка, чтобы большие боковые
// отступы секции не ломали раскладку и связанные hover-эффекты карточек.
const columns = columnCountForWidth(listWidth);
const colWidth = (listWidth - gap * (columns - 1)) / columns;
const heights = Array(columns).fill(0);
const positions = new Map();
allItems.forEach((item) => {
item.style.width = `${colWidth}px`;
const minHeight = Math.min(...heights);
const colIndex = heights.indexOf(minHeight);
const x = colIndex * (colWidth + gap);
const y = heights[colIndex];
item.style.transform = `translate(${x}px, ${y}px)`;
heights[colIndex] = y + item.offsetHeight + gap;
positions.set(item, { y, h: item.offsetHeight });
});
fullHeight = Math.max(Math.max(...heights) - gap, 0);
collapsedHeight = 0;
baseItems.forEach((item) => {
const p = positions.get(item);
if (!p) return;
collapsedHeight = Math.max(collapsedHeight, p.y + p.h);
});
list.style.height = `${expanded ? fullHeight : collapsedHeight}px`;
}
function relayout() {
if (rafId) window.cancelAnimationFrame(rafId);
rafId = window.requestAnimationFrame(performLayout);
}
function relayoutAfterThemeChange() {
relayout();
if (document.fonts?.ready) {
document.fonts.ready.then(relayout);
}
window.requestAnimationFrame(() => {
relayout();
window.requestAnimationFrame(relayout);
});
}
window.addEventListener("site-theme-applied", relayoutAfterThemeChange, { passive: true });
window.addEventListener("resize", relayout, { passive: true });
window.addEventListener("load", relayout, { once: true });
relayout();
function setExpanded(next) {
expanded = next;
list.style.height = `${expanded ? fullHeight : collapsedHeight}px`;
}
return { list, relayout, setExpanded, extraItems };
}