Created
May 21, 2025 21:42
-
-
Save Blankeos/6eba1116f4533525042e60ef7638e83e to your computer and use it in GitHub Desktop.
masonry.svelte
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!-- Masonry.svelte --> | |
| <!-- @component | |
| I got this component from a library called svelte-mason. Just upgraded it to Svelte 5. | |
| --> | |
| <script lang="ts" generics="Item"> | |
| import type { Snippet } from 'svelte'; | |
| type Props = { | |
| animate?: boolean; | |
| calcCols?: (masonryWidth: number, minColWidth: number, gap: number) => number; | |
| class?: string; | |
| columnClass?: string; | |
| duration?: number; | |
| gap?: number; | |
| getId?: (item: Item) => string | number; | |
| idKey?: string; | |
| items?: Item[]; | |
| masonryHeight?: number; | |
| masonryWidth?: number; | |
| maxColWidth?: number; | |
| minColWidth?: number; | |
| style?: string; | |
| children?: Snippet<[{ idx: number; item: Item }]>; | |
| }; | |
| let { | |
| items = [], | |
| animate = true, | |
| calcCols = (masonryWidth: number, minColWidth: number, gap: number): number => { | |
| return Math.min(items.length, Math.floor((masonryWidth + gap) / (minColWidth + gap)) || 1); | |
| }, | |
| class: className = '', | |
| columnClass = ``, | |
| duration = 200, | |
| gap = 20, | |
| getId = (item: Item): string | number => { | |
| if (typeof item === `number`) return item; | |
| if (typeof item === `string`) return item; | |
| return (item as Record<string, string | number>)[idKey as any]!; | |
| }, | |
| idKey = `id`, | |
| masonryHeight = $bindable(0), | |
| masonryWidth = $bindable(0), | |
| maxColWidth = 500, | |
| minColWidth = 330, | |
| style = ``, | |
| children, | |
| }: Props = $props(); | |
| $effect(() => { | |
| if (maxColWidth < minColWidth) { | |
| console.warn(`svelte-bricks: maxColWidth (${maxColWidth}) < minColWidth (${minColWidth}).`); | |
| } | |
| }); | |
| let nCols = $derived(calcCols(masonryWidth, minColWidth, gap)); | |
| let itemsToCols = $derived( | |
| items.reduce( | |
| (cols: [Item, number][][], item, idx) => { | |
| cols[idx % cols.length]?.push([item, idx]); | |
| return cols; | |
| }, | |
| Array(nCols).fill(null).map(() => []) // prettier-ignore | |
| ) | |
| ); | |
| </script> | |
| <div | |
| class="masonry {className}" | |
| bind:clientWidth={masonryWidth} | |
| bind:clientHeight={masonryHeight} | |
| style="gap: {gap}px; {style}" | |
| > | |
| {#each itemsToCols as col, i (i)} | |
| <div class="col {columnClass}" style="gap: {gap}px; max-width: {maxColWidth}px;"> | |
| {#if animate} | |
| {#each col as [item, idx] (getId(item))} | |
| <div | |
| in:fade={{ delay: 100, duration }} | |
| out:fade={{ delay: 0, duration }} | |
| animate:flip={{ duration }} | |
| > | |
| {@render children?.({ idx: idx, item: item })} | |
| <!-- <slot {idx} {item}> | |
| <span>{item}</span> | |
| </slot> --> | |
| </div> | |
| {/each} | |
| {:else} | |
| {#each col as [item, idx] (getId(item))} | |
| {@render children?.({ idx: idx, item: item })} | |
| {/each} | |
| {/if} | |
| </div> | |
| {/each} | |
| </div> | |
| <style> | |
| :where(div.masonry) { | |
| display: flex; | |
| justify-content: center; | |
| overflow-wrap: anywhere; | |
| box-sizing: border-box; | |
| } | |
| :where(div.masonry div.col) { | |
| display: grid; | |
| height: max-content; | |
| width: 100%; | |
| } | |
| </style> | |
| <!-- --------- USAGE --------- --> | |
| <script> | |
| let minColWidth = $state(250); | |
| let maxColWidth = $state(800); | |
| let gap = $state(10); | |
| let width = $state(0); | |
| let height = $state(0); | |
| </script> | |
| <Masonry | |
| animate={false} | |
| items={testimonials} | |
| getId={(item) => item.id} | |
| minColWidth={minColWidth} | |
| maxColWidth={maxColWidth} | |
| gap={gap} | |
| bind:masonryWidth={width} | |
| bind:masonryHeight={height} | |
| > | |
| {#snippet children(prop)} | |
| <MasonItem /> <!-- This can be literally any item you want rendered. --> | |
| {/snippet} | |
| </Masonry> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment