更新记录

1.2.0(2025-12-12)

Android 兼容性优化

  • 修复布局计算:修复 Grid 布局模式下 textExtraHeight 为 0 时的计算逻辑问题
  • 优化宽度测量:优化 Android 平台下的容器宽度获取方式,提高兼容性
  • 文档更新:更新 readme,增加 Android 平台网格布局的最佳实践说明

1.1.1(2025-12-01)

重大更新

  • UTS 重构:核心布局引擎使用 UTS (Uni Type Script) 完全重写,在 uni-app x 中提供原生级性能
  • uni-app x 支持:全面支持 uni-app x (Android/iOS),提供专属 .uvue 组件
  • 双引擎架构:同时保留 JS 引擎和 UTS 引擎,完美兼容普通 uni-app 项目和 uni-app x 项目

优化与修复

  • Android 兼容性:修复 UTSJSONObject 类型访问问题,优化 DOM 测量逻辑
  • iOS 兼容性:修复 CSS 样式兼容性问题(移除不支持的 display: block 等)
  • H5 兼容性:修复模板闭合标签和容器样式问题
  • 布局优化
    • 优化动态卡片高度预估算法,减少视觉空白
    • 修复瀑布流垂直间距计算逻辑
    • 优化 Flexbox 布局,确保多端视觉一致性

1.1.0(2025-12-01)

查看更多

平台兼容性

uni-app(3.6.15)

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

uni-app x(3.6.15)

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

其他

多语言 暗黑模式 宽屏模式

hy-virtual-waterfall

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

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

✨ 特性

  • 🚀 极致性能 - 虚拟渲染技术,支持10000+数据流畅滚动
  • UTS原生 - 基于 UTS 重构核心逻辑,在 uni-app x 中提供原生级性能
  • 🎨 多种布局 - 支持瀑布流、网格、均匀等多种布局模式
  • 📐 智能计算 - 自动计算最优布局,自适应容器宽度
  • 🔄 动态尺寸 - 支持内容高度自动测量和更新
  • 📱 多端支持 - 完美支持 H5、小程序、App(vue/nvue/uvue)
  • 🎯 易用性 - 简洁的 API 设计,丰富的配置选项
  • 📝 TypeScript - 完整的类型定义支持

📢 uni-app x 支持说明

本插件已全面适配 uni-app x,提供原生级的渲染性能。

  • 组件路径uni_modules/hy-virtual-waterfall/components/hy-virtual-waterfall/hy-virtual-waterfall.uvue
  • 核心逻辑:使用 UTS (Uni Type Script) 重写,运行在原生层
  • 类型安全:提供完整的 UTS 类型定义 (LayoutConfig, VirtualConfig)

⚠️ Android 平台注意事项

在 uni-app x (Android) 中,由于严格的类型限制,访问 UTSJSONObject 属性时必须使用方括号语法

// ❌ 错误写法 (Android 编译报错)
<text>{{ item.title }}</text>

// ✅ 正确写法
<text>{{ item['title'] }}</text>

⚠️ Android 网格布局注意事项

在 Android 平台使用 网格布局 (Grid) 时,为确保正确渲染,请务必保证数据项的 aspectRatio1 (正方形),否则可能会显示为瀑布流错乱样式:

// Android 网格模式数据生成示例
{
  aspectRatio: 1.0, // 🔴 必须显式设置为 1.0
  image: '...',
  title: '...'
}

🎯 快速开始

<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-app x 兼容写法
      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>

引入组件

uni-app x 项目 (.uvue):

import HyVirtualWaterfall from '@/uni_modules/hy-virtual-waterfall/components/hy-virtual-waterfall/hy-virtual-waterfall.uvue';
import type { LayoutConfig, VirtualConfig } from '@/uni_modules/hy-virtual-waterfall/js_sdk/uts/types.uts';

export default {
  components: {
    HyVirtualWaterfall
  }
}

普通 uni-app 项目 (.vue):

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. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率: