稳定列
已经排进某一列的项会一直留在该列。图片加载、内容展开只会让同一列下方的项上下平移,不会跨列跳动。
通用瀑布流:按容器宽度自适应列数(也支持固定列数与断点),最短列优先且已排入的项保持在原列,图片加载只让同列下方平移;自带高度错落的骨架屏,整面共享一道对角线微光,加载更多时骨架落在最短的几列底部,并与到达的内容原地交叉淡出;新项自上而下错开上浮淡入,列数变化、筛选、排序与增删时各项沿可打断的弹簧滑到新位置;最短一列接近底部时触发无限加载,配套 MasonryImage 与 MasonrySkeleton。
查看 Masonry 组件的基本效果
使用 Masonry 组件的基础示例
1import { Masonry, MasonryImage } from "@/components/qiuye-ui/masonry";
1<Masonry2 items={photos}3 getItemKey={(photo) => photo.id}4 renderItem={(photo) => (5 <MasonryImage6 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
列数
排布策略 strategy
好的过渡不会被注意到,它只是让变化显得理所当然。
已经排进某一列的项会一直留在该列。图片加载、内容展开只会让同一列下方的项上下平移,不会跨列跳动。
本周新增
1,284
骨架是排在真实项之后的虚拟项,同样走最短列优先。加载更多时它们自然填进最短的几列底部,内容到达后在几乎相同的位置淡入,读起来就像骨架变成了内容。
留白不是空,是给内容呼吸的空间。
平均停留
3m 42s
瀑布流里最短的一列最先见底,所以加载哨兵放在最短一列的底部。
先让它能用,再让它好用,最后让它好看。
系统开启减少动态效果时,各项直接到位,入场与退场只做短暂的淡入淡出,骨架不再扫光。
renderSkeleton 组合多个 MasonrySkeleton(封面、标题、两行摘要),每一块按自己在瀑布流中的位置对齐微光, 整面墙仍是同一道从左上扫向右下的波。列数由容器宽度决定而不是视口,所以把瀑布流放进侧栏、对话框或分栏里同样正确。列宽 = (容器宽 − 间距 × (列数 − 1)) / 列数。断点的键同样是容器宽度,手机上也可以保持两列。
新挂载的项在 useLayoutEffect 中同步测量高度并定位,首帧绘制前就已经在正确的位置。服务端渲染时先用 CSS 多列近似排布,水合后再切换到精确定位。
所有项共用一个 ResizeObserver,读取 borderBoxSize,不受入场动画 transform 的影响。同一个回调里的多项变化只排布一次,滚动加载大量内容时也不会为每一项各自创建观察者。
定位层只写 transform,不挂类名;入场与退场放在内容层,两者互不覆盖。位置用弹簧过渡,已有动画时从当前值与速度重新出发,可以随时改变目标。
DOM 顺序就是数据顺序,Tab 键的顺序与数据一致,不受视觉上的列影响。
所有微光共用同一个 CSS 动画。每个微光元素按它在瀑布流中的 x + y 换算出负的 animation-delay,并校正动画实际开始的时间,整面墙上就是一道连续的对角线波,而不是各块各闪各的。
系统开启减少动态效果时,位置直接到位,入场与退场只做约 200ms 的淡入淡出,骨架只保留静态底色。这里的卡片展开也会直接到位,不做高度过渡。
每一项所在的列会被缓存,图片加载、内容展开带来的高度变化都不会让它换列。展开这张卡片试试:只有同一列下方的卡片跟着向下让位,其他列纹丝不动,收起后再原样复位。