File size: 2,993 Bytes
edb4af2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
<script lang="ts">
import { tick } from 'svelte';
export let items: any[] = []; // pass in data if it's dynamically updated
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) => {
/* get the post relayout number of columns */
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 the number of columns has changed */
if (grid.ncol !== ncol || grid.mod) {
/* update number of columns */
grid.ncol = ncol;
/* revert to initial positioning, no margin */
grid.items.forEach((c) => c.style.removeProperty('margin-top'));
/* if we have more than one column */
if (grid.ncol > 1) {
grid.items.slice(ncol).forEach((c, i) => {
let prev_fin =
grid.items[i].getBoundingClientRect().bottom /* bottom edge of item above */,
curr_ini = c.getBoundingClientRect().top; /* top edge of current item */
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(); /* initial load */
}
};
$: if (masonryElement) {
calcGrid([masonryElement]);
}
$: if (items) {
// update if items are changed
masonryElement = masonryElement; // refresh 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); // minmax(Min(20em, 100%), 1fr);
$s: var(--grid-gap); // .5em;
-->
<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>
|