Skip to content

Instantly share code, notes, and snippets.

@johannschopplich
Last active July 4, 2023 12:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johannschopplich/33de21d027d8266878878462e175aef3 to your computer and use it in GitHub Desktop.
Save johannschopplich/33de21d027d8266878878462e175aef3 to your computer and use it in GitHub Desktop.
Vue 3 component for masonry grid polyfill
<script setup lang="ts">
const props = withDefaults(
defineProps<{
columnMaxWidth?: string;
}>(),
{
columnMaxWidth: "25rem",
}
);
const container = ref<HTMLElement | undefined>();
let properties:
| {
items: HTMLElement[];
gap: number;
columns: number;
count: number;
}
| undefined;
const debouncedUpdateGridItems = useDebounceFn(updateGridItems, 100);
useResizeObserver(container, debouncedUpdateGridItems);
watch(
() => props.columnMaxWidth,
() => updateGridItems()
);
onMounted(() => {
if (!container.value) return;
// Bail if masonry layouts are already supported by the browser
// if (getComputedStyle(container.value).gridTemplateRows === "masonry") return;
const { rowGap } = getComputedStyle(container.value);
properties = {
items: [...container.value.children] as HTMLElement[],
gap: rowGap !== "normal" ? parseFloat(rowGap) : 0,
columns: 0,
count: 0,
};
updateGridItems();
});
function updateGridItems() {
if (!container.value || !properties) return;
const columns = getComputedStyle(container.value).gridTemplateColumns.split(
" "
).length;
for (const column of properties.items) {
const { height } = column.getBoundingClientRect();
const h = height.toString();
if (h !== column.dataset?.h) {
column.dataset.h = h;
properties.count++;
}
}
// Bail if the number of columns hasn't changed
if (properties.columns === columns && !properties.count) return;
// Update the number of columns
properties.columns = columns;
// Revert to initial positioning, no margin
for (const { style } of properties.items) style.removeProperty("margin-top");
// If we have more than one column
if (properties.columns > 1) {
for (const [index, column] of properties.items.slice(columns).entries()) {
// Bottom edge of item above
const { bottom: prevBottom } =
properties.items[index].getBoundingClientRect();
// Top edge of current item
const { top } = column.getBoundingClientRect();
column.style.marginTop = `${prevBottom + properties.gap - top}px`;
}
}
properties.count = 0;
}
</script>
<template>
<!-- grid-rows-[masonry] -->
<div
ref="container"
class="grid grid-cols-[repeat(auto-fit,minmax(min(var(--masonry-col-max-w),100%),1fr))] justify-center children:self-start"
:style="`--masonry-col-max-w: ${columnMaxWidth}`"
>
<slot />
</div>
</template>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment