npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dream-masonry

v0.1.2

Published

High-performance virtualized masonry grid layout for React. Pinterest-style layouts with infinite scroll, virtual rendering, and responsive columns. Faster than Masonic.

Readme

DreamMasonry

npm version bundle size license

A high-performance virtualized masonry grid layout for React. Create Pinterest-style layouts, image galleries, and card grids with infinite scroll and virtual rendering for buttery smooth performance with 10,000+ items.

Perfect for: Photo galleries, Pinterest clones, card layouts, image grids, portfolio sites, e-commerce product grids, and any masonry/waterfall layout.

Why DreamMasonry?

| Feature | DreamMasonry | Masonic | react-masonry-css | |---------|--------------|---------|-------------------| | Virtualized rendering | Yes | Yes | No | | Infinite scroll built-in | Yes | No | No | | Float64Array layout | Yes | No | No | | Bundle size | ~12KB | ~14KB | ~3KB | | Responsive columns | Yes | Yes | Yes | | Custom scroll container | Yes | Yes | No | | Headless hooks | Yes | Yes | No | | Zero dependencies | Yes | No | Yes |

Features

  • Float64Array layout engine — Column height tracking uses typed arrays for faster numeric operations than plain JavaScript arrays
  • Virtualized rendering — Only items within the viewport (plus configurable overscan) are rendered to the DOM
  • GPU-accelerated positioning — Items use translate3d transforms and CSS containment (contain: strict on container, layout style paint on items)
  • Hysteresis-based scroll updates — Configurable threshold prevents re-render thrashing during scroll
  • RAF-throttled scroll handler — At most one layout update per animation frame
  • Built-in infinite scroll — Optional pagination hook with debounce and threshold control
  • Custom scroll containers — Works with window or any scrollable element via ref
  • Headless hooks — Use the layout engine without the component for fully custom rendering
  • Fully configurable — Gutter size, column counts, column widths, scroll thresholds, and overscan are all customizable
  • Tiny bundle — ~12KB with no dependencies beyond React

Install

npm install dream-masonry

Quick Start

import { DreamMasonry } from 'dream-masonry';

type Photo = {
  id: string;
  src: string;
  width: number;
  height: number;
};

function Gallery({ photos }: { photos: Photo[] }) {
  return (
    <DreamMasonry
      items={photos}
      maxColumnCount={4}
      renderItem={(photo) => (
        <img
          src={photo.src}
          alt=""
          style={{ width: '100%', height: '100%', objectFit: 'cover' }}
        />
      )}
    />
  );
}

Infinite Scroll

function InfiniteGallery() {
  const [items, setItems] = useState<Photo[]>([]);
  const [hasMore, setHasMore] = useState(true);
  const [loading, setLoading] = useState(false);

  const loadMore = async () => {
    setLoading(true);
    const next = await fetchPhotos(items.length);
    setItems((prev) => [...prev, ...next.data]);
    setHasMore(next.hasMore);
    setLoading(false);
  };

  return (
    <DreamMasonry
      items={items}
      renderItem={(photo) => <img src={photo.src} alt="" />}
      hasMore={hasMore}
      isFetchingMore={loading}
      onLoadMore={loadMore}
      scrollThreshold={2000}
      renderLoader={() => <div>Loading...</div>}
      renderEmpty={() => <div>No photos yet</div>}
    />
  );
}

Custom Layout

<DreamMasonry
  items={items}
  renderItem={(item) => <Card item={item} />}
  maxColumnCount={6}
  minColumnCount={1}
  minColumnWidth={180}
  gutterSize={8}
  overscan={1200}
  hysteresis={50}
/>

Custom Scroll Container

function ScrollablePanel() {
  const scrollRef = useRef<HTMLDivElement>(null);

  return (
    <div ref={scrollRef} style={{ height: '100vh', overflow: 'auto' }}>
      <DreamMasonry
        items={items}
        renderItem={(item) => <Card item={item} />}
        scrollContainer={scrollRef}
      />
    </div>
  );
}

Headless Usage

useGrid — Full virtualization without the component

import { useGrid } from 'dream-masonry';

function CustomGrid({ items }) {
  const { containerRef, dimensions, visibleItems, totalHeight } = useGrid({
    items,
    maxColumnCount: 4,
    minColumnCount: 2,
    minColumnWidth: 200,
    gutterSize: 12,
    overscan: 800,
    hysteresis: 50,
  });

  return (
    <div
      ref={containerRef}
      style={{ height: totalHeight, position: 'relative' }}
    >
      {visibleItems.map(({ item, pos, transform }) => (
        <div
          key={item.id}
          style={{
            position: 'absolute',
            transform,
            width: dimensions!.columnWidth,
            height: pos.height,
          }}
        >
          <YourComponent item={item} />
        </div>
      ))}
    </div>
  );
}

usePositioner — Layout math only, no DOM

import { usePositioner } from 'dream-masonry';

function LayoutDebugger({ items, width }) {
  const { positions, totalHeight, dimensions } = usePositioner({
    items,
    containerWidth: width,
    maxColumnCount: 3,
    gutterSize: 10,
  });

  // positions is an array of { column, top, left, height } for each item
  // Use for canvas rendering, SSR, testing, or anything non-DOM
}

useInfiniteScroll — Standalone pagination

import { useInfiniteScroll } from 'dream-masonry';

useInfiniteScroll({
  fetchNextPage: loadMore,
  hasNextPage: true,
  isFetchingNextPage: false,
  threshold: 1500,
  useWindow: true,
});

API

<DreamMasonry> Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | items | T[] | required | Array of items. Each must have id: string and optionally width/height or aspectRatio | | renderItem | (item: T, index: number) => ReactNode | required | Render function for each grid cell | | maxColumnCount | number | 5 | Maximum number of columns | | minColumnCount | number | 2 | Minimum number of columns | | minColumnWidth | number | 240 | Minimum column width in pixels before reducing column count | | gutterSize | number | 1.5 | Gap between items in pixels | | isLoading | boolean | false | Show loader state | | hasMore | boolean | false | Whether more items can be loaded | | isFetchingMore | boolean | false | Whether a load is in progress | | onLoadMore | () => Promise<unknown> | — | Called when scroll nears the bottom | | scrollContainer | MutableRefObject<HTMLElement> | — | Custom scroll container (defaults to window) | | overscan | number | 1000 | Pixels above/below viewport to pre-render | | hysteresis | number | 100 | Minimum scroll distance before re-calculating visible items | | scrollThreshold | number | 1500 | Distance from bottom in pixels to trigger onLoadMore | | renderLoader | () => ReactNode | — | Custom loading state | | renderEmpty | () => ReactNode | — | Custom empty state | | className | string | — | Container class | | style | CSSProperties | — | Container style (merged with internal styles) |

useGrid Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | items | T[] | required | Array of grid items | | maxColumnCount | number | 5 | Maximum columns | | minColumnCount | number | 2 | Minimum columns | | minColumnWidth | number | 240 | Minimum column width in px | | gutterSize | number | 1.5 | Gap between items in px | | overscan | number | 1000 | Pre-render buffer in px | | hysteresis | number | 100 | Scroll threshold before update | | scrollContainer | RefObject<HTMLElement> | — | Custom scroll element |

Returns: { containerRef, dimensions, positions, totalHeight, visibleItems, validItems }

usePositioner Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | items | T[] | required | Array of grid items | | containerWidth | number | required | Container width in px | | maxColumnCount | number | 5 | Maximum columns | | minColumnCount | number | 2 | Minimum columns | | minColumnWidth | number | 240 | Minimum column width in px | | gutterSize | number | 1.5 | Gap between items in px |

Returns: { dimensions, positions, totalHeight, validItems }

Item Shape

Items must satisfy:

type GridItem = {
  id: string;
  width?: number;       // intrinsic width
  height?: number;      // intrinsic height
  aspectRatio?: number; // width / height (e.g. 16/9 = 1.778)
};

The grid resolves item height in this order:

  1. width + height — calculates aspect ratio from dimensions
  2. aspectRatio — uses the ratio directly (useful when you only have the ratio, not raw dimensions)
  3. Neither — renders as a square

How It Works

  1. Column calculation — Container width is divided into columns respecting minColumnWidth, minColumnCount, and maxColumnCount constraints, with configurable gutterSize gaps
  2. Masonry positioning — Items are placed in the shortest column using Float64Array for O(items x columns) layout
  3. Viewport culling — Only items intersecting [scrollTop - overscan, scrollTop + viewportHeight + overscan] are rendered
  4. Scroll throttling — A requestAnimationFrame loop checks scroll position, but only triggers a React update when the viewport moves more than the hysteresis threshold
  5. Resize handling — A debounced ResizeObserver recalculates column dimensions when the container width changes

License

MIT