Skip to content

Waterfall 瀑布流 ​

多列不等高排版容器(商品流、图文动态、壁纸站):列宽等分,条目由 #item 插槽完全自定义,新条目按各列实际高度自动放入最短列。数据用 v-model 双向绑定,也提供 append / remove / update / clear 命令式方法。

平台兼容性 ​

App Web 微信小程序
✓ ✓ ✓

放置依据是各列实时渲染高度;条目内的图片等异步资源加载完成后列高会变化,已放置条目不重排,后续条目自动纠正(渐进填充模型)。批次收尾有列高稳定探测;图片就绪需要精确控制时用 readyKey 显式门控 end。

基础用法 ​

html
<n-waterfall v-model="flowList">
	<template #item="{ item, index }">
		<view class="goods-card">
			<image class="goods-img" :src="item['image'] as string" mode="widthFix" />
			<text class="goods-title">{{ item['title'] }}</text>
		</view>
	</template>
</n-waterfall>
js
const flowList = ref<UTSJSONObject[]>([]);
// 追加数据:直接改数组(组件 deep 监听自动排布)
flowList.value = flowList.value.concat(nextBatch);

命令式方法 ​

js
const waterfall = ref<NWaterfallComponentPublicInstance | null>(null);

waterfall.value?.append(items);        // 追加一批(等价拼接 v-model 数组,并回写数据源)
waterfall.value?.remove(3);            // 按 nodeKey 字段值移除一条
waterfall.value?.update(3, { title: "新标题" }); // 就地合并更新一条
waterfall.value?.clear();              // 清空(作废同批次全部在途回调与定时器)

方法操作后组件回写 v-model 数据源,父级不回写也不影响组件内排布;remove / update 针对不存在的 id 无操作(不触发回写)。

图片就绪门控(readyKey) ​

图片归 #item 插槽所有,就绪信号也由插槽发——在 image 的 @load / @error 里把条目的就绪字段置真,end 会等全部条目就绪才触发:

html
<n-waterfall v-model="flowList" ready-key="imgReady" @end="onPlaced">
	<template #item="{ item }">
		<view class="goods-card">
			<image class="goods-img" :src="item['image'] as string" mode="widthFix"
				@load="onImgReady(item)" @error="onImgReady(item)" />
			<text class="goods-title">{{ item['title'] }}</text>
		</view>
	</template>
</n-waterfall>
js
function onImgReady(item: UTSJSONObject): void {
	item["imgReady"] = true; // deep 监听同步进组件;无图条目构造数据时预置 imgReady: true
}

契约是严格的:字段缺失或假值都算未就绪,end 不会发出(字段名写错会立刻暴露而不是静默降级)。已知条目高度的业务(等比图写死 height、固定文案行)不需要该门——插槽里写死高度即等价于预设高的免等待用法。

加载更多 ​

html
<n-waterfall v-model="flowList" @end="onPlaced">
	<template #item="{ item }">
		<view class="card"><!-- 条目内容 --></view>
	</template>
</n-waterfall>
<n-loadmore :status="loadStatus" @loadmore="loadNext" />

end 在一批数据全部放置完成且列高稳定(readyKey 非空时还需全部就绪)后触发,可在此解锁下一次请求;addTime 控制逐条放置的间隔(默认 100ms),图片多时可调大让列高先稳定,批次收尾的稳定探测也复用该间隔(夹取 50-1000ms)。

API ​

Props ​

属性 类型 说明
modelValue UTSJSONObject[] 数据源(受控);追加自动排布、移除自动删列、同 key 换新对象自动同步内容、新数据插到已有条目之前自动全量重排,默认 []
column number 列数;非法值(NaN / ±Infinity / 小于 1)回落 1 列并发 error 事件,非整数向下取整;改变后已有数据全量重新排布,默认 2
gutter number 相邻列的水平间距(px),只作用于列间,不影响两侧外缘,默认 8
leftGap number 左侧外缘间距(px),与列间距独立;页面自带留白时保持 0,默认 0
rightGap number 右侧外缘间距(px),与列间距独立;页面自带留白时保持 0,默认 0
itemGap number 相邻条目的垂直间距(px),挂在条目节点上,默认 8
addTime number 单条数据放入队列的间隔(ms);图片异步加载会改变列高,间隔越大分布越准;批次收尾的列高稳定探测复用该间隔(夹取 50-1000ms),默认 100
nodeKey string 条目唯一标识字段名,用于数据 diff 与 remove/update 定位,默认 "id"
readyKey string 条目就绪标记字段名;非空时 end 额外等待全部已放置条目该字段为真值(见「图片就绪门控」),空串关闭,默认 ""
customClass string 根节点额外 class,默认 ""
customStyle string 根节点行内样式,最后应用,默认 ""

Events ​

事件 参数 说明
update:modelValue items: UTSJSONObject[] append/remove/update/clear 操作后触发,参数为操作后的完整数据数组
end — 一批数据全部放置完成且列高稳定(readyKey 非空时还需全部条目就绪)后触发;仅在实际放置过数据后发出
init 布局就绪或 column/gutter/leftGap/rightGap 变化后触发,itemWidth 为单列内容宽度(px);窗口尺寸变化自动重测重发(值去重);隐藏容器(宽 0)不发假值
error NWaterfallError 配置或测量错误:prop 为 column(列数非法,回落 1 列)/ measure(列高测量不可得,本轮降级轮转分布,每放置周期至多一次)/ nodeKey(update 迁移键冲突,该条目退化为不可按键定位)

Slots ​

插槽 参数 说明
item item: UTSJSONObject, index: number 条目内容;index 为条目在数据源中的下标

Expose ​

方法 说明
append(items: UTSJSONObject[]): void 追加一批数据(重复 nodeKey 跳过)
remove(id: string | number): void 按 nodeKey 字段值移除一条;余下条目的插槽 index 重编为当前源下标
update(id: string | number, data: UTSJSONObject): void 按 nodeKey 字段值就地合并更新一条;改动 nodeKey 字段值时身份索引原子迁移(渲染身份不变)
clear(): void 清空全部数据与放置队列(作废在途回调与定时器)

行为边界 ​

  • 同 key 换新对象会同步内容:父级以相同 nodeKey 替换条目对象时,插槽渲染新对象内容,不重排(高度变化由后续放置消化)——等价一次 update(id, item) 全量合并。
  • 缺 nodeKey 字段的条目(keyless)按对象引用绑定:引用仍在数据源内则条目存活(删首个无键条目会保留真正存活的那个);引用消失条目删除;新引用追加渲染。remove / update 仍要求条目带唯一标识(与主流实现一致)。
  • 新数据插到已有条目之前(头部插入)会全量重排:显示序回到数据源序;重排复用渲染身份(uid),列内未移动的条目 DOM 保持稳定,跨列移动的条目随列容器重建(声明式列表跨列移动的固有行为),数据内容连续不丢。
  • 运行时改 nodeKey 会以新字段对现有数据全量重排(身份体系重建)。
  • 同批/同源重复 key 只渲染首个,回写数据源保留原始数组;update 把标识改成其他条目已占用的值时会发 error(prop: nodeKey),该条目内容照常渲染但不再能按键定位。
  • 列高稳定探测有趟数上限(20 趟):条目内容自带布局动画导致高度永不稳定时到顶放行;readyKey 门不受该上限约束。

实现说明 ​

与参考实现(同类瀑布流组件)的机制对照:

机制 参考做法 本库做法
数据通道 命令式 append/remove/update/clear;或 v-model 数据源 两者合并:v-model 为数据源(deep 监听兼容父级 push 同一数组),append/remove/update/clear 为命令式入口,内部作用于镜像数组后回写 update:modelValue,两条通道幂等不重放
分布模型 批量渲染隐藏虚拟项测高后分配;或逐条入队按列高放置 逐条放置(渐进填充):量实时列高 → 放最短列 → 等渲染 → addTime 间隔放下一条;每次放置都以实时列高为准,无累计簿记,图片后加载导致的列高漂移由后续放置自动纠正
列高测量 虚拟项渲染 300ms 后批量测;或逐条查询(有限重试) 每条放置前 boundingClientRect 一次查全部列容器;查询不可得时有限重试(3 趘 × 50ms),仍失败发 error 并按轮转分布降级(内容完整、各列均衡)
批次完成通知 全部条目布局完成后 end 队列排空后做列高稳定探测(连续两趟读数一致,趟数上限 20),readyKey 非空时再等全部条目就绪,之后才发 end——解锁时点是真实就绪
身份模型 业务键即渲染键(字符串空间共用) 分治:内部渲染身份为单调递增 uid(永不字符串化),业务 nodeKey 只在注册表换算;无键条目按源对象引用绑定;update 改业务标识时同 uid 原子迁移索引
条目就绪协调 条目组件内嵌 image 的 load/error 回调驱动重排 图片归插槽所有:readyKey 字段 + 插槽 @load/@error 置位,end 等全部就绪;已知高度直接在插槽写死即免等待
头部插入 显式 isBefore 开关 + 已渲染队列重建全量重排 v-model diff 直接看到源序:新增条目源下标先于存活条目即自动全量重排(无需开关);重排复用 uid,追加与同键替换不触碰已有条目 DOM,跨列移动随列容器重建
间距 列间距单一 gutter;或列距/上距/左右距分设 gutter(列间)+ leftGap/rightGap(两侧外缘,默认 0——外缘留白归还页面布局)+ itemGap(条目垂直间距,逐条挂在条目节点,末条目带一份尾距、各列等量补偿不影响列高比较)
条目宽度通知 initParam 计算后发 init({itemWidth})(窗口宽推算,一次性) init({itemWidth}):实测根宽计算,column/gutter/leftGap/rightGap 变化与窗口尺寸变化自动重测重发(值去重);隐藏容器不发假值
列数变化 清空数据需重新喂入;或声明不可动态修改 保留数据全量重排:清列后按新列数把数据源重新逐条放置(uid 复用)
条目移除/更新 remove(id)/update(id,data) 同名方法;update 为合并新对象(不改入参)并回写数据源,就地重渲染不重排
刷新重置 resetLoadmore 重置布局状态 clear + 重新喂数据等价覆盖(clear 作废同批次全部在途回调与定时器)
非法列数 未处理(Infinity 会死循环 / NaN 渲染 0 列) 归一化:非有限值或下取整 <1 回落 1 列并发 error;非整数向下取整
未采纳 显式 isBefore 开关(v-model 源序检测已覆盖该能力,无需使用者声明插入方位);条目自带图片测高/预设高 prop(条目由插槽全自定义,已知高度的图在插槽里直接写死高度即可);fui 双组件形态(容器 + 条目组件,本库单组件 + 插槽承接条目能力) 见左列说明

记录 · 思考 · 创作