85 lines
2.8 KiB
JavaScript
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 };
|
|
}
|