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

react-hybrid-masonry

v1.0.1

Published

A high-performance React virtual scrolling masonry layout library with support for waterfall and equal-height layouts

Downloads

99

Readme

React Hybrid Masonry (中文文档)

English | 简体中文

一个高性能的 React 虚拟滚动瀑布流布局库,支持瀑布流和等高布局两种模式。

🎮 在线演示

查看在线演示

体验所有三种布局模式的交互式示例和代码片段。

✨ 特性

  • 🚀 高性能虚拟滚动 - 只渲染可视区域内的元素,支持海量数据展示
  • 📐 多种布局模式
    • 瀑布流布局(Pinterest 风格) - 不等宽不等高,自动找到最短列放置
    • 等高布局(Google Photos 风格) - 每行高度相同,宽度按原始比例自适应填满整行
    • 动态布局 - 支持根据接口返回的数据动态切换布局类型
  • 🎯 智能预加载 - 基于 IntersectionObserver 实现的无限滚动,支持自定义预加载距离
  • 🎨 完全可定制 - 支持自定义渲染函数、加载状态、"没有更多"提示、间距等
  • 📱 响应式设计 - 使用 ResizeObserver 自动适配容器宽度变化
  • RAF 优化 - 使用 requestAnimationFrame 优化滚动性能,避免卡顿
  • 🔧 TypeScript 支持 - 完整的 TypeScript 类型定义
  • 🪶 零外部依赖 - 除了 React 不依赖任何第三方库

📦 安装

npm install react-hybrid-masonry
# 或者
yarn add react-hybrid-masonry
# 或者
pnpm add react-hybrid-masonry

🎯 快速开始

1. 瀑布流布局 (Pinterest 风格)

适用于图片墙、商品列表等场景。

import { VirtualMasonry } from 'react-hybrid-masonry';

function ImageGallery() {
  // 数据加载函数
  const loadData = async (page: number, pageSize: number) => {
    const response = await fetch(`/api/images?page=${page}&size=${pageSize}`);
    const data = await response.json();
    return {
      data: data.items,      // 数据数组
      hasMore: data.hasMore, // 是否还有更多数据
    };
  };

  return (
    <VirtualMasonry
      loadData={loadData}
      pageSize={30}
      minColumnWidth={200}    // 最小列宽
      maxColumnWidth={350}    // 最大列宽(可选)
      gap={16}                // 间距
      renderItem={(item) => (
        <div
          style={{
            position: 'absolute',
            left: item.x,      // 组件会自动计算位置
            top: item.y,
            width: item.width,
            height: item.height,
          }}
        >
          <img src={item.url} alt={item.title} />
        </div>
      )}
    />
  );
}

2. 等高布局 (Google Photos 风格)

适用于相册、时间线等需要整齐对齐的场景。

import { FullWidthEqualHeightMasonry } from 'react-hybrid-masonry';

function PhotoAlbum() {
  const loadData = async (page: number, pageSize: number) => {
    const response = await fetch(`/api/photos?page=${page}&size=${pageSize}`);
    const data = await response.json();
    return {
      data: data.items,
      hasMore: data.hasMore,
    };
  };

  return (
    <FullWidthEqualHeightMasonry
      loadData={loadData}
      pageSize={30}
      targetRowHeight={245}      // 目标行高
      sizeRange={[230, 260]}     // 行高范围[最小, 最大]
      maxItemWidth={975}         // 单个项目最大宽度
      gap={8}
      renderItem={(item) => (
        <div
          style={{
            position: 'absolute',
            left: item.x,
            top: item.y,
            width: item.width,
            height: item.height,
          }}
        >
          <img src={item.url} alt={item.title} />
        </div>
      )}
    />
  );
}

3. 动态布局

根据接口返回的数据动态切换布局类型。

import { DynamicMasonryView } from 'react-hybrid-masonry';

function DynamicGallery() {
  const loadData = async (page: number, pageSize: number) => {
    const response = await fetch(`/api/content?page=${page}&size=${pageSize}`);
    const data = await response.json();

    // 第一次加载时返回布局类型
    if (page === 1) {
      return {
        data: data.items,
        hasMore: data.hasMore,
        isMasonry: data.layoutType === 'waterfall', // true=瀑布流, false=等高
      };
    }

    // 后续加载不需要返回布局类型
    return {
      data: data.items,
      hasMore: data.hasMore,
    };
  };

  return (
    <DynamicMasonryView
      loadData={loadData}
      pageSize={30}
      // 瀑布流配置
      waterfallConfig={{
        minColumnWidth: 200,
        maxColumnWidth: 350,
        gap: 16,
      }}
      // 等高布局配置
      equalHeightConfig={{
        targetRowHeight: 245,
        sizeRange: [230, 260],
        gap: 8,
      }}
      renderItem={(item, isMasonry) => (
        <div
          style={{
            position: 'absolute',
            left: item.x,
            top: item.y,
            width: item.width,
            height: item.height,
            borderRadius: isMasonry ? '8px' : '4px', // 可以根据布局类型调整样式
          }}
        >
          <img src={item.url} alt={item.title} />
        </div>
      )}
    />
  );
}

📖 API 文档

VirtualMasonry

瀑布流布局组件,适合不等宽不等高的内容。

Props

| 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | loadData | (page: number, pageSize: number) => Promise<{data: any[], hasMore: boolean}> | 必填 | 数据加载函数,返回 Promise | | renderItem | (item: any, index: number) => React.ReactNode | 必填 | 渲染每个项目的函数 | | pageSize | number | 50 | 每页加载的数据量 | | minColumnWidth | number | 200 | 最小列宽(px) | | maxColumnWidth | number | - | 最大列宽(px),不设置则不限制 | | gap | number | 16 | 项目间距(px) | | buffer | number | 1500 | 可视区域外的缓冲区大小(px),越大渲染的内容越多 | | loadMoreThreshold | number | 800 | 预加载阈值(px),距离底部多远时触发加载 | | mapSize | (raw: any) => {width: number, height: number} | - | 映射数据的宽高字段 | | renderLoader | (totalHeight: number) => React.ReactNode | - | 自定义加载状态组件 | | renderNoMore | (totalHeight: number) => React.ReactNode | - | 自定义"没有更多"提示组件 |

FullWidthEqualHeightMasonry

等高布局组件,每行高度相同,宽度自适应。

Props

| 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | loadData | (page: number, pageSize: number) => Promise<{data: any[], hasMore: boolean}> | 必填 | 数据加载函数 | | renderItem | (item: any) => React.ReactNode | 必填 | 渲染函数,item 包含 x, y, width, height | | pageSize | number | 50 | 每页数据量 | | targetRowHeight | number | 245 | 目标行高,实际会在 sizeRange 范围内调整 | | sizeRange | [number, number] | [230, 260] | 行高范围 [最小高度, 最大高度] | | maxItemWidth | number | 975 | 单个项目的最大宽度,避免图片过宽 | | maxStretchRatio | number | 1.5 | 最大拉伸比例,避免图片过度变形 | | gap | number | 8 | 项目间距(px) | | buffer | number | 1500 | 缓冲区大小(px) | | loadMoreThreshold | number | 500 | 预加载阈值(px) | | mapSize | (raw: any) => {width: number, height: number} | - | 映射数据的宽高字段 | | renderLoader | (totalHeight: number) => React.ReactNode | - | 自定义加载状态 | | renderNoMore | (totalHeight: number) => React.ReactNode | - | 自定义"没有更多"提示 |

DynamicMasonryView

动态布局组件,可以根据数据动态切换瀑布流和等高布局。

Props

| 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | isMasonry | boolean | - | 受控模式:是否使用瀑布流布局 | | defaultIsMasonry | boolean | true | 非受控模式:默认布局类型 | | loadData | LoadDataFn | 必填 | 数据加载函数,第一次可返回 isMasonry | | renderItem | (item: any, isMasonry: boolean) => React.ReactNode | 必填 | 渲染函数,第二个参数表示当前布局类型 | | waterfallConfig | WaterfallConfig | {} | 瀑布流配置对象 | | equalHeightConfig | EqualHeightConfig | {} | 等高布局配置对象 | | pageSize | number | 50 | 每页数据量 | | mapSize | (raw: any) => {width: number, height: number} | - | 映射宽高字段 | | renderInitialLoader | () => React.ReactNode | - | 初始加载状态(获取布局类型时) | | onLayoutTypeLoaded | (isMasonry: boolean) => void | - | 布局类型加载完成回调 | | onError | (error: Error) => void | - | 错误回调 |

LoadDataFn 类型

type LoadDataFn<T = any> = (
  page: number,
  pageSize: number,
) => Promise<{
  data: T[];
  hasMore: boolean;
  isMasonry?: boolean; // 仅第一次调用时返回
}>;

🎨 自定义样式

自定义加载状态和"没有更多"提示

<VirtualMasonry
  loadData={loadData}
  renderItem={renderItem}
  renderLoader={(totalHeight) => (
    <div
      style={{
        position: 'absolute',
        top: totalHeight,
        left: 0,
        right: 0,
        height: 100,
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
      }}
    >
      <div className="custom-loader">加载中...</div>
    </div>
  )}
  renderNoMore={(totalHeight) => (
    <div
      style={{
        position: 'absolute',
        top: totalHeight,
        left: 0,
        right: 0,
        height: 50,
        textAlign: 'center',
        color: '#999',
        paddingTop: '20px',
      }}
    >
      - 没有更多数据了 -
    </div>
  )}
/>

自定义项目样式

<VirtualMasonry
  loadData={loadData}
  renderItem={(item) => (
    <div
      className="gallery-item"
      style={{
        position: 'absolute',
        left: item.x,
        top: item.y,
        width: item.width,
        height: item.height,
        borderRadius: '8px',
        overflow: 'hidden',
        boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
        transition: 'transform 0.2s, box-shadow 0.2s',
      }}
      onMouseEnter={(e) => {
        e.currentTarget.style.transform = 'scale(1.05)';
        e.currentTarget.style.boxShadow = '0 8px 16px rgba(0,0,0,0.2)';
      }}
      onMouseLeave={(e) => {
        e.currentTarget.style.transform = 'scale(1)';
        e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
      }}
    >
      <img
        src={item.url}
        alt={item.title}
        style={{
          width: '100%',
          height: '100%',
          objectFit: 'cover',
        }}
      />
      <div className="overlay">
        <h3>{item.title}</h3>
        <p>{item.description}</p>
      </div>
    </div>
  )}
/>

🔧 高级用法

映射数据格式

如果你的数据格式与默认不同,可以使用 mapSize 来映射宽高字段:

<VirtualMasonry
  loadData={loadData}
  mapSize={(item) => ({
    width: item.imageWidth,   // 自定义宽度字段名
    height: item.imageHeight, // 自定义高度字段名
  })}
  renderItem={renderItem}
/>

默认支持的字段名:

  • 宽度: width / w / imgW
  • 高度: height / h / imgH

性能优化建议

1. 调整缓冲区大小

buffer 属性控制可视区域外渲染多少内容。值越大,滚动时越流畅,但内存占用越高。

<VirtualMasonry
  buffer={1500}  // 默认值,可根据实际情况调整
  // ...
/>

2. 调整预加载阈值

loadMoreThreshold 控制距离底部多远时开始加载下一页。

<VirtualMasonry
  loadMoreThreshold={800}  // 距离底部 800px 时开始加载
  // ...
/>

3. 使用 React.memo 优化渲染

const GalleryItem = React.memo(({ item }) => (
  <div
    style={{
      position: 'absolute',
      left: item.x,
      top: item.y,
      width: item.width,
      height: item.height,
    }}
  >
    <img src={item.url} alt={item.title} />
  </div>
));

<VirtualMasonry
  renderItem={(item) => <GalleryItem item={item} />}
/>

4. 图片懒加载

配合浏览器原生懒加载或第三方库:

<VirtualMasonry
  renderItem={(item) => (
    <div style={{...}}>
      <img
        src={item.url}
        alt={item.title}
        loading="lazy"  // 浏览器原生懒加载
      />
    </div>
  )}
/>

🏃 运行 Demo

# 克隆仓库
git clone https://github.com/yourusername/react-hybrid-masonry.git
cd react-hybrid-masonry

# 安装依赖
npm install

# 启动开发服务器
npm run dev

# 构建库
npm run build

访问 http://localhost:3000 查看完整的 demo 示例。

📝 更新日志

v1.0.0 (2025-01-XX)

  • 🎉 首次发布
  • ✨ 支持瀑布流布局
  • ✨ 支持等高布局
  • ✨ 支持动态布局切换
  • 🚀 虚拟滚动优化
  • 📖 完整的 TypeScript 类型

🤝 贡献指南

我们欢迎所有形式的贡献!

  1. Fork 本仓库
  2. 创建你的特性分支 (git checkout -b feature/AmazingFeature)
  3. 提交你的改动 (git commit -m 'Add some AmazingFeature')
  4. 推送到分支 (git push origin feature/AmazingFeature)
  5. 开启一个 Pull Request

📄 许可证

本项目采用 MIT 许可证。

📮 联系方式

如果你有任何问题或建议:

🙏 致谢

感谢所有为这个项目做出贡献的开发者!


如果这个项目对你有帮助,欢迎给个 ⭐️ Star!