|
<script lang="ts"> |
|
import { tick } from 'svelte'; |
|
export let items: any[] = []; |
|
|
|
type GridItem = { |
|
_el: HTMLElement; |
|
gap: number; |
|
items: HTMLElement[]; |
|
ncol: number; |
|
mod: number; |
|
}; |
|
|
|
let grids: GridItem[] = []; |
|
let masonryElement: HTMLElement; |
|
|
|
const refreshLayout = async () => { |
|
masonryElement.querySelectorAll('img').forEach((img) => { |
|
if (img.complete) { |
|
img.classList.add('loaded'); |
|
} |
|
}); |
|
|
|
grids.forEach(async (grid) => { |
|
|
|
let ncol = getComputedStyle(grid._el).gridTemplateColumns.split(' ').length; |
|
|
|
grid.items.forEach((c) => { |
|
let new_h = c.getBoundingClientRect().height; |
|
|
|
if (new_h !== Number(c.dataset.h)) { |
|
c.dataset.h = String(new_h); |
|
grid.mod++; |
|
} |
|
}); |
|
|
|
|
|
if (grid.ncol !== ncol || grid.mod) { |
|
|
|
grid.ncol = ncol; |
|
|
|
grid.items.forEach((c) => c.style.removeProperty('margin-top')); |
|
|
|
if (grid.ncol > 1) { |
|
grid.items.slice(ncol).forEach((c, i) => { |
|
let prev_fin = |
|
grid.items[i].getBoundingClientRect().bottom , |
|
curr_ini = c.getBoundingClientRect().top; |
|
|
|
c.style.marginTop = `${prev_fin + grid.gap - curr_ini}px`; |
|
}); |
|
} |
|
|
|
grid.mod = 0; |
|
} |
|
}); |
|
}; |
|
|
|
const calcGrid = async (_masonryArr: HTMLElement[]) => { |
|
await tick(); |
|
if (_masonryArr.length && getComputedStyle(_masonryArr[0]).gridTemplateRows !== 'masonry') { |
|
grids = _masonryArr.map((grid) => { |
|
return { |
|
_el: grid, |
|
gap: parseFloat(getComputedStyle(grid).rowGap), |
|
items: [...(grid.childNodes as unknown as HTMLElement[])].filter( |
|
(c) => c.nodeType === 1 && +getComputedStyle(c).gridColumnEnd !== -1 |
|
), |
|
ncol: 0, |
|
mod: 0 |
|
}; |
|
}); |
|
refreshLayout(); |
|
} |
|
}; |
|
|
|
$: if (masonryElement) { |
|
calcGrid([masonryElement]); |
|
} |
|
|
|
$: if (items) { |
|
|
|
masonryElement = masonryElement; |
|
|
|
if (masonryElement) { |
|
const images = masonryElement.querySelectorAll('img'); |
|
|
|
images.forEach((img) => { |
|
img.removeEventListener('load', refreshLayout); |
|
img.addEventListener('load', refreshLayout); |
|
}); |
|
} |
|
} |
|
</script> |
|
|
|
<svelte:window on:resize={refreshLayout} on:load={refreshLayout} /> |
|
|
|
<div bind:this={masonryElement} class="__grid--masonry"> |
|
<slot /> |
|
</div> |
|
|
|
<!-- |
|
$w: var(--col-width); |
|
$s: var(--grid-gap); |
|
--> |
|
<style> |
|
:global(.__grid--masonry) { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fit, var(--col-width, minmax(Min(20em, 100%), 1fr))); |
|
grid-template-rows: masonry; |
|
justify-content: start; |
|
grid-gap: var(--grid-gap, 0.5em); |
|
} |
|
|
|
:global(.__grid--masonry > *) { |
|
align-self: start; |
|
} |
|
</style> |
|
|