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>