更新记录

1.0.1(2025-10-30) 下载此版本

修改示例说明文件

1.0.0(2025-10-30) 下载此版本


平台兼容性

uni-app(3.6.12)

Vue2 Vue2插件版本 Vue3 Vue2插件版本 Chrome Chrome插件版本 Safari Safari插件版本 app-vue app-vue插件版本 app-nvue app-nvue插件版本 Android Android插件版本 iOS iOS插件版本 鸿蒙 鸿蒙插件版本
1.0.0 1.0.0 1.0.0 1.0.0 1.0.0 1.0.0 5.0 1.0.0 12 1.0.0 1.0.0
微信小程序 微信小程序插件版本 支付宝小程序 支付宝小程序插件版本 抖音小程序 抖音小程序插件版本 百度小程序 百度小程序插件版本 快手小程序 快手小程序插件版本 京东小程序 京东小程序插件版本 鸿蒙元服务 鸿蒙元服务插件版本 QQ小程序 QQ小程序插件版本 飞书小程序 飞书小程序插件版本 快应用-华为 快应用-华为插件版本 快应用-联盟 快应用-联盟插件版本
1.0.0 1.0.0 1.0.0 1.0.0 1.0.0 1.0.0 1.0.0 1.0.0 1.0.0 1.0.0 1.0.0

uni-app x(3.6.12)

Chrome Chrome插件版本 Safari Safari插件版本 Android Android插件版本 iOS iOS插件版本 鸿蒙 鸿蒙插件版本 微信小程序 微信小程序插件版本
1.0.0 1.0.0 5.0 1.0.0 12 1.0.0 1.0.0 1.0.0

其他

多语言 暗黑模式 宽屏模式

hy-virtual-waterfall

高性能虚拟列表与瀑布流组件

专为 uni-app 打造的虚拟渲染瀑布流组件,支持海量数据流畅滚动

✨ 特性

  • 🚀 极致性能 - 虚拟渲染技术,支持10000+数据流畅滚动
  • 🎨 多种布局 - 支持瀑布流、网格、均匀等多种布局模式
  • 📐 智能计算 - 自动计算最优布局,自适应容器宽度
  • 🔄 动态尺寸 - 支持内容高度自动测量和更新
  • 高性能 - GPU加速、缓存优化、批量处理
  • 📱 多端支持 - 完美支持 H5、小程序、App(vue/nvue)
  • 🎯 易用性 - 简洁的 API 设计,丰富的配置选项
  • 📝 TypeScript - 完整的类型定义支持

🎯 快速开始

<template>
  <view class="container">
    <hy-virtual-waterfall
      :data="feedList"
      :layout-config="layoutConfig"
      :virtual-config="virtualConfig"
      :loading="loading"
      :has-more="hasMore"
      @load-more="loadMoreData"
      @item-click="onItemClick"
      @item-visible="onItemVisible"
    >
      <template #item="{ item, index }">
        <view class="feed-item">
          <image :src="item.cover" mode="widthFix" class="feed-cover" />
          <view class="feed-content">
            <text class="feed-title">{{ item.title }}</text>
            <text class="feed-desc">{{ item.description }}</text>
            <view class="feed-meta">
              <text class="feed-author">{{ item.author }}</text>
              <text class="feed-time">{{ item.time }}</text>
            </view>
          </view>
        </view>
      </template>
    </hy-virtual-waterfall>
  </view>
</template>

<script>
export default {
  data() {
    return {
      feedList: [],
      loading: false,
      hasMore: true,
      layoutConfig: {
        type: 'waterfall',
        columns: 2,
        columnGap: 12,
        rowGap: 12
      },
      virtualConfig: {
        enabled: true,
        overscan: 3,
        estimatedHeight: 300,
        useDynamicHeight: true
      }
    };
  },

  mounted() {
    this.loadInitialData();
  },

  methods: {
    async loadInitialData() {
      this.loading = true;
      try {
        const data = await this.fetchFeedData(0);
        this.feedList = data;
      } catch (error) {
        console.error('加载数据失败:', error);
      } finally {
        this.loading = false;
      }
    },

    async loadMoreData() {
      if (this.loading || !this.hasMore) return;

      this.loading = true;
      try {
        const newData = await this.fetchFeedData(this.feedList.length);
        this.feedList = [...this.feedList, ...newData];
        this.hasMore = newData.length > 0;
      } catch (error) {
        console.error('加载更多失败:', error);
      } finally {
        this.loading = false;
      }
    },

    onItemClick(item, index) {
      console.log('点击项目:', item, index);
      uni.navigateTo({
        url: `/pages/detail?id=${item.id}`
      });
    },

    onItemVisible(item, index) {
      // 记录曝光数据
      this.trackExposure(item.id);
    }
  }
};
</script>

<style scoped>
.container {
  height: 100vh;
}

.feed-item {
  background: #fff;
  border-radius: 12rpx;
  overflow: hidden;
  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
}

.feed-cover {
  width: 100%;
  display: block;
}

.feed-content {
  padding: 24rpx;
}

.feed-title {
  display: block;
  font-size: 28rpx;
  font-weight: bold;
  color: #333;
  margin-bottom: 12rpx;
  line-height: 1.4;
}

.feed-desc {
  display: block;
  font-size: 24rpx;
  color: #666;
  line-height: 1.4;
  margin-bottom: 16rpx;
}

.feed-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.feed-author {
  font-size: 22rpx;
  color: #999;
}

.feed-time {
  font-size: 22rpx;
  color: #999;
}
</style>

手动引入组件

import HyVirtualWaterfall from '@/uni_modules/hy-virtual-waterfall/components/hy-virtual-waterfall/hy-virtual-waterfall.vue';

export default {
  components: {
    HyVirtualWaterfall
  }
}

📖 基础示例

社交动态瀑布流

<template>
  <hy-virtual-waterfall
    :data="socialFeeds"
    :layout-config="{
      type: 'waterfall',
      columns: 2,
      columnGap: 16,
      rowGap: 16
    }"
    @load-more="loadMoreFeeds"
  >
    <template #item="{ item }">
      <view class="social-card">
        <image :src="item.user.avatar" class="user-avatar" />
        <text class="user-name">{{ item.user.name }}</text>
        <image 
          v-for="(img, idx) in item.images" 
          :key="idx"
          :src="img" 
          mode="widthFix" 
          class="feed-image"
        />
        <text class="feed-text">{{ item.content }}</text>
      </view>
    </template>
  </hy-virtual-waterfall>
</template>

商品网格布局

<template>
  <hy-virtual-waterfall
    :data="products"
    :layout-config="{
      type: 'grid',
      columns: 3,
      columnGap: 8,
      rowGap: 8
    }"
    @item-click="onProductClick"
  >
    <template #item="{ item }">
      <view class="product-card">
        <image :src="item.image" mode="aspectFill" class="product-image" />
        <text class="product-name">{{ item.name }}</text>
        <text class="product-price">¥{{ item.price }}</text>
      </view>
    </template>
  </hy-virtual-waterfall>
</template>

📚 API 文档

Props 属性

属性名 类型 默认值 说明
data Array [] 数据源
layoutConfig Object {...} 布局配置
virtualConfig Object {...} 虚拟配置
loading Boolean false 加载状态
hasMore Boolean true 是否有更多数据
emptyText String '暂无数据' 空状态文本
showBackTop Boolean true 是否显示回到顶部
height String '100vh' 容器高度

Events 事件

事件名 参数 说明
load-more - 加载更多数据
item-click (item, index) 项目点击事件
item-visible (item, index) 项目可见事件
scroll (scrollTop) 滚动事件
scroll-to-bottom - 滚动到底部
scroll-to-top - 滚动到顶部

layoutConfig 布局配置

属性名 类型 默认值 说明
type String 'waterfall' 布局类型:'waterfall' 瀑布流 / 'grid' 网格
columns Number 2 列数
columnGap Number 12 列间距(px)
rowGap Number 12 行间距(px)
minColumnWidth Number 100 最小列宽(px),用于响应式布局
maxColumnWidth Number 400 最大列宽(px),用于响应式布局
textExtraHeight Number - 文字内容额外高度(px),用于预估项目高度。瀑布流建议120,网格布局建议0

textExtraHeight 说明:

  • 瀑布流布局:如果内容包含标题、描述等文字,建议设置为 120,为文字内容预留空间
  • 网格布局:如果文字是 overlay 覆盖在图片上,设置为 0
  • 不设置:自动根据 item.data 是否有 title/description/author 判断(有则100,无则0)
// 瀑布流配置示例
layoutConfig: {
  type: 'waterfall',
  columns: 2,
  columnGap: 12,
  rowGap: 12,
  minColumnWidth: 120,
  maxColumnWidth: 400,
  textExtraHeight: 120 // 为文字内容预留空间
}

// 网格配置示例
layoutConfig: {
  type: 'grid',
  columns: 3,
  columnGap: 8,
  rowGap: 8,
  minColumnWidth: 80,
  maxColumnWidth: 200,
  textExtraHeight: 0 // 文字是overlay,不需要额外高度
}

virtualConfig 虚拟配置

属性名 类型 默认值 说明
enabled Boolean true 是否启用虚拟滚动
overscan Number 3 预渲染倍数
estimatedHeight Number 300 预估项目高度(px)

布局算法说明

本组件参考 iOS UICollectionView 的瀑布流布局算法,具有以下特点:

  1. 布局缓存:位置一旦计算完成就不再改变,避免滚动时布局跳动
  2. 精确预估:基于 aspectRatio 一次性计算准确高度,不依赖后续测量
  3. 列高度持久化:上拉加载新数据时自动继承当前列高度,无缝衔接
  4. 减少测量:只在首次加载前10个item时允许微调,后续item布局锁定

最佳实践:

// ✅ 推荐:提供准确的 aspectRatio
const feedList = [
  {
    id: 1,
    image: 'https://example.com/image.jpg', // 400x600
    aspectRatio: 600 / 400, // 1.5 (高度/宽度)
    title: '标题',
    description: '描述'
  }
];

// ❌ 不推荐:aspectRatio 不准确会导致布局偏差
const feedList = [
  {
    id: 1,
    image: 'https://example.com/image.jpg',
    aspectRatio: 1, // 实际图片是1.5,会导致计算错误
  }
];

为什么需要准确的 aspectRatio?

  • iOS 方式依赖预估高度一次性布局,完全不会在运行时动态调整
  • 这样可以避免滚动时布局跳动,消除累积误差
  • 如果 aspectRatio 不准确,会导致预留空间不足或过多

如何调整间距?

如果发现间距不合适,请调整配置参数:

// 1. 调整 textExtraHeight(文字内容高度)
basicLayoutConfig: {
  textExtraHeight: 140 // 默认140px,根据实际内容调整
}

// 2. 调整 rowGap(行间距)
basicLayoutConfig: {
  rowGap: 12 // 增大或减小间距
}

// 3. 确保 aspectRatio 准确
// 图片 400x600 → aspectRatio = 600/400 = 1.5
// 图片 400x500 → aspectRatio = 500/400 = 1.25

不要依赖动态测量! 本组件已完全禁用运行时高度更新,以避免累积误差。

Methods 方法

通过 ref 调用组件方法:

// 滚动到指定项目
this.$refs.waterfall.scrollToItem(itemId, 100);

// 添加数据
this.$refs.waterfall.appendData(newData);

// 重新计算布局
this.$refs.waterfall.recalculateLayout();

// 回到顶部
this.$refs.waterfall.scrollToTop();

🚀 高级功能示例

动态列数适配

// 根据屏幕宽度动态计算列数
computed: {
  responsiveLayoutConfig() {
    const screenWidth = uni.getSystemInfoSync().screenWidth;
    const columns = screenWidth > 750 ? 3 : 2;

    return {
      columns,
      columnGap: 12,
      rowGap: 12,
      maxColumnWidth: 280,
      minColumnWidth: 200
    };
  }
}

图片预加载优化

// 在项目可见时预加载图片
onItemVisible(item, index) {
  if (item.images && item.images.length > 0) {
    item.images.forEach(imgUrl => {
      const img = new Image();
      img.src = imgUrl; // 触发预加载
    });
  }
}

🔧 配置项详解

LayoutConfig (布局配置)

属性 类型 默认值 说明
type String 'waterfall' 布局类型:waterfall | grid | uniform
columns Number 2 列数
columnGap Number 12 列间距(px)
rowGap Number 12 行间距(px)
maxColumnWidth Number - 最大列宽(px)
minColumnWidth Number - 最小列宽(px)

VirtualConfig (虚拟配置)

属性 类型 默认值 说明
enabled Boolean true 是否启用虚拟渲染
overscan Number 5 预渲染屏幕外的项目数量
estimatedHeight Number 200 预估项目高度(px)
useDynamicHeight Boolean true 是否使用动态高度

🎨 使用场景

  • 📸 图片瀑布流展示(如 Pinterest、小红书)
  • 🛍️ 商品列表展示(如淘宝、京东)
  • 📰 新闻资讯流(如今日头条)
  • 🎬 视频列表展示(如抖音、B站)
  • 📱 社交动态流(如微博、朋友圈)
  • 🎨 作品集展示(如Dribbble、Behance)

⚡ 性能优化

  1. 虚拟渲染 - 只渲染可见区域,大幅减少 DOM 节点
  2. GPU 加速 - 使用 transform3d 开启硬件加速
  3. 智能缓存 - 缓存布局和尺寸信息,避免重复计算
  4. 批量处理 - 批量更新操作,减少重绘次数
  5. 防抖节流 - 滚动事件防抖处理

如果这个组件对你有帮助,请给个⭐️支持一下!

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议