Skip to main content

Virtual Masonry

Virtualised masonry from the mates-virtual package. Only the visible tiles are rendered — perfect for galleries with hundreds or thousands of items.

Quick start
import { virtualMasonry } from "mates-virtual";

html`
  <div style="height:32rem;overflow:auto">
    ${virtualMasonry(
      photos,
      (p) => p.id,
      (p) => html`<img src=${p.url} style="width:100%;height:100%;object-fit:cover" />`,
      {
        getAspectRatio: (p) => p.width / p.height,
        itemSize: "300px",
        gap: "8px",
      },
    )}
  </div>
`

200 items virtualised

import { html, isSSR, nothing, renderApp } from 'mates';
import { virtualMasonry } from 'mates-virtual';
import { Badge } from 'mates-ui';

const TAGS = ["Nature", "City", "People", "Abstract", "Travel", "Food", "Tech", "Art"];

const ITEMS = Array.from({ length: 200 }, (_, i) => ({
id: String(i),
title: "Photo " + (i + 1),
img: "https://picsum.photos/seed/" + (i + 1) + "/400/" + (220 + (i % 5) * 40),
height: 220 + (i % 5) * 40,
tag: TAGS[i % TAGS.length],
likes: ((i * 7919 + 31337) % 500) + 10,
}));

const App = () => () => html`
${isSSR() ? nothing : html`
<div style="height:32rem;border:1px solid var(--md-color-border);border-radius:var(--md-radius-md);overflow:auto;max-width:52rem;width:100%;">
${virtualMasonry(
ITEMS,
(item) => item.id,
(item) => html`
<div style="background:var(--md-surface-card);border:0.06rem solid var(--md-color-border);border-radius:var(--md-radius-md);overflow:hidden;">
<img src=${item.img} style="width:100%;height:${item.height}px;object-fit:cover;display:block;" loading="lazy" alt=${item.title} />
<div style="padding:var(--md-space-2) var(--md-space-3);display:flex;align-items:center;justify-content:space-between;">
<span style="font-size:var(--md-text-xs);color:var(--md-on-surface-muted);">${item.title}</span>
${Badge(item.tag, { variant: "neutral" })}
</div>
</div>
`,
{ getAspectRatio: (item) => 400 / item.height, itemSize: "220px", gap: "12px" },
)}
</div>
`}
`;

virtualMasonry

import { virtualMasonry } from "mates-virtual";
Prop Type Default Description
attr AttrMap HTML attributes on the root element.
classes string | string[] Extra root classes — joined and appended after built-in classes.
data DataAttrMap Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
gap `${number}px` 8px Gap between items.
getAspectRatio* (item: T) => number Returns width/height aspect ratio — used to derive item height from column width without DOM measurement.
id string Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
items* T[] The full data array.
itemSize `${number}px` 300px Column width in px. Item height is derived from the aspect ratio.
keyFn (item: T) => string | number Optional unique key extractor for efficient DOM reconciliation.
on OnEventMap DOM event handlers on the root element.
renderItem* (item: T, index: number) => TemplateResult How to render each item.
scroller boolean When true the virtualizer host element scrolls itself — give it an explicit height via CSS.
style StyleMap Inline CSS on the root element.

Caveats

  • The container must have a fixed height with `overflow: auto` — the directive measures it.
  • Provide `getAspectRatio` so the masonry layout can compute item heights from the column width without DOM measurement.
  • `itemSize` controls column width; item height is automatically derived from the aspect ratio.