QiuYe UI
内容与媒体

Masonry

通用瀑布流:按容器宽度自适应列数(也支持固定列数与断点),最短列优先且已排入的项保持在原列,图片加载只让同列下方平移;自带高度错落的骨架屏,整面共享一道对角线微光,加载更多时骨架落在最短的几列底部,并与到达的内容原地交叉淡出;新项自上而下错开上浮淡入,列数变化、筛选、排序与增删时各项沿可打断的弹簧滑到新位置;最短一列接近底部时触发无限加载,配套 MasonryImage 与 MasonrySkeleton。

瀑布流masonry骨架屏无限加载自适应列数图片墙布局过渡

快速预览

查看 Masonry 组件的基本效果

基本用法

使用 Masonry 组件的基础示例

导入组件

1import { Masonry, MasonryImage } from "@/components/qiuye-ui/masonry";

使用组件

1<Masonry
2 items={photos}
3 getItemKey={(photo) => photo.id}
4 renderItem={(photo) => (
5 <MasonryImage
6 src={photo.src}
7 alt={photo.alt}
8 width={photo.width} // 只用来得出宽高比,加载前后不重排
9 height={photo.height}
10 className="rounded-xl"
11 />
12 )}
13 minColumnWidth={220} // 按容器宽度自动计算列数
14 gap={16}
15 loading={loading} // 首屏铺满骨架;加载更多时骨架落在最短的几列底部
16 hasMore={hasMore}
17 onLoadMore={loadMore} // 最短一列接近底部时触发
18/>

组件演示

查看组件的各种使用方式和效果

图片瀑布流 · 无限加载
首屏铺满高度错落的骨架,整面墙共享一道对角线微光;图片按宽高比占位,加载后柔和淡入。 滚到底部时骨架出现在最短的几列底部——正是新内容将要出现的位置。

已加载 0 / 60 张 · 照片来自 Unsplash

参数调试
拖动虚线框右边缘改变容器宽度:宽度连续变化时各项跟手,列数变化时沿弹簧滑到新位置。 列数按容器宽度计算,与视口无关。
测量中…
#1
#2
#3
#4
#5
#6
#7
#8
#9
#10
#11
#12
#13
#14
#15
#16
#17
#18

列数

最小列宽 minColumnWidth120px
最大列数 maxColumns6
间距 gap12px

排布策略 strategy

筛选与排序
图片、引用、笔记、数据卡混排,文字卡没有宽高比提示,高度由内容测量得出。 筛选与排序时各项沿弹簧滑到新位置,可以随时打断;移除的卡片在原位置缩小淡出。
示例照片:林间
林间88
好的过渡不会被注意到,它只是让变化显得理所当然。
— 动效笔记

稳定列

已经排进某一列的项会一直留在该列。图片加载、内容展开只会让同一列下方的项上下平移,不会跨列跳动。

本周新增

1,284

示例照片:薄雾
薄雾93

骨架屏

骨架是排在真实项之后的虚拟项,同样走最短列优先。加载更多时它们自然填进最短的几列底部,内容到达后在几乎相同的位置淡入,读起来就像骨架变成了内容。

示例照片:静谧
静谧59
留白不是空,是给内容呼吸的空间。
— 排版手记

平均停留

3m 42s

无限加载

瀑布流里最短的一列最先见底,所以加载哨兵放在最短一列的底部。

示例照片:微光
微光75
先让它能用,再让它好用,最后让它好看。
— 产品随想
示例照片:海风
海风68

减少动态效果

系统开启减少动态效果时,各项直接到位,入场与退场只做短暂的淡入淡出,骨架不再扫光。

自定义骨架
renderSkeleton 组合多个 MasonrySkeleton(封面、标题、两行摘要),每一块按自己在瀑布流中的位置对齐微光, 整面墙仍是同一道从左上扫向右下的波。
children 模式 · 内容展开
静态内容可以直接把带 key 的元素作为子元素传入。文字卡片没有宽高比提示, 展开某张卡片时,只有同一列下方的卡片向下让位,不会跨列跳动。
排布

按容器宽度计算列数

列数由容器宽度决定而不是视口,所以把瀑布流放进侧栏、对话框或分栏里同样正确。列宽 = (容器宽 − 间距 × (列数 − 1)) / 列数。断点的键同样是容器宽度,手机上也可以保持两列。

渲染

首帧没有闪烁

新挂载的项在 useLayoutEffect 中同步测量高度并定位,首帧绘制前就已经在正确的位置。服务端渲染时先用 CSS 多列近似排布,水合后再切换到精确定位。

性能

共享一个 ResizeObserver

所有项共用一个 ResizeObserver,读取 borderBoxSize,不受入场动画 transform 的影响。同一个回调里的多项变化只排布一次,滚动加载大量内容时也不会为每一项各自创建观察者。

动效

定位层与内容层分开

定位层只写 transform,不挂类名;入场与退场放在内容层,两者互不覆盖。位置用弹簧过渡,已有动画时从当前值与速度重新出发,可以随时改变目标。

无障碍

键盘顺序

DOM 顺序就是数据顺序,Tab 键的顺序与数据一致,不受视觉上的列影响。

骨架

同步的微光

所有微光共用同一个 CSS 动画。每个微光元素按它在瀑布流中的 x + y 换算出负的 animation-delay,并校正动画实际开始的时间,整面墙上就是一道连续的对角线波,而不是各块各闪各的。

无障碍

减少动态效果

系统开启减少动态效果时,位置直接到位,入场与退场只做约 200ms 的淡入淡出,骨架只保留静态底色。这里的卡片展开也会直接到位,不做高度过渡。

排布

稳定列

每一项所在的列会被缓存,图片加载、内容展开带来的高度变化都不会让它换列。展开这张卡片试试:只有同一列下方的卡片跟着向下让位,其他列纹丝不动,收起后再原样复位。