Infinite Masonry
将不同高度项目排列为多列,并在可见范围推进时请求更多内容。
适用场景
- 当界面需要以下行为时使用 Infinite Masonry:将不同高度项目排列为多列,并在可见范围推进时请求更多内容。
安装
选择安装方式,用法示例将同步更新导入路径。
$ bunx --bun shadcn-vue@2.8.2 add https://orvui.efu.me/r/infinite-masonry.json
首次使用时,将安装的 src/components/orvui/styles.css 导入项目样式入口。
示例
预览
Visual index
Scroll to load the next page
12 items
packages/vue/src/blocks/infinite-masonry/demo.vue
<script setup lang="ts">
import { onBeforeUnmount, ref } from "vue";
import { InfiniteMasonry } from "@/components/orvui/blocks/infinite-masonry";
const scenes = [
["Soft geometry", "Architecture", "1511818966892-d7d671e672a2", 260],
["Open horizon", "Landscape", "1500530855697-b586d89ba3ee", 190],
["Working rhythm", "Workspace", "1497366754035-f200968a6e72", 230],
["Shared table", "Studio", "1524758631624-e2822e304c36", 300],
["In session", "People", "1521737711867-e3b97375f902", 210],
["After hours", "Office", "1504384308090-c894fdcc538d", 280],
] as const;
function page(start: number, count: number) {
return Array.from({ length: count }, (_, offset) => {
const index = start + offset,
[title, category, photo, imageHeight] = scenes[index % scenes.length];
return {
id: `gallery-${index}`,
title: `${title} ${Math.floor(index / scenes.length) + 1}`,
category,
image: `https://images.unsplash.com/photo-${photo}?auto=format&fit=crop&w=720&q=80`,
imageHeight,
};
});
}
const items = ref(page(0, 12)),
loading = ref(false);
let timer: ReturnType<typeof setTimeout> | undefined;
function load() {
if (loading.value || items.value.length >= 42) return;
loading.value = true;
timer = setTimeout(() => {
items.value.push(
...page(items.value.length, Math.min(6, 42 - items.value.length)),
);
loading.value = false;
}, 650);
}
onBeforeUnmount(() => clearTimeout(timer));
</script>
<template>
<div class="w-full max-w-5xl">
<div class="mb-3 flex items-end justify-between gap-4 px-1">
<div>
<p class="text-sm font-semibold text-foreground">Visual index</p>
<p class="mt-0.5 text-xs text-muted-foreground">
Scroll to load the next page
</p>
</div>
<p class="font-mono text-xs tabular-nums text-muted-foreground">
{{ items.length }} items
</p>
</div>
<InfiniteMasonry
:items="items"
:get-item-key="(item) => item.id"
:has-more="items.length < 42"
:loading="loading"
:estimate-size="(item) => item.imageHeight + 66"
aria-label="Visual inspiration gallery"
class="h-[34rem]"
@load-more="load"
><template #default="{ item }"
><figure
class="overflow-hidden rounded-2xl border border-border bg-card"
>
<div
class="relative w-full overflow-hidden"
:style="{ height: item.imageHeight + 'px' }"
>
<img
:src="item.image"
:alt="item.title"
class="absolute inset-0 size-full object-cover"
/>
</div>
<figcaption class="flex items-start justify-between gap-3 p-3">
<p class="text-sm font-medium text-foreground">{{ item.title }}</p>
<p
class="shrink-0 text-[10px] uppercase tracking-wider text-muted-foreground"
>
{{ item.category }}
</p>
</figcaption>
</figure></template
></InfiniteMasonry
>
</div>
</template>
packages/vue/src/blocks/infinite-masonry/src/InfiniteMasonry.vue
状态与事件
此示例组合没有声明成对的受控属性与 update 事件。使用已列出的属性、原生事件和插槽组合内容;不要为它添加未公开的 v-model。
API 参考
itemsreadonly T[]—getItemKey(item: T, index: number) => string | number | bigint—hasMorebooleanfalseonLoadMore?() => void | Promise<void>—loading?booleanfalseerror?boolean | null | string | number | void | VNode<RendererNode, RendererElement, { [key: string]: any; }> | VNodeArrayChildren | ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> | FunctionalComponent<any, {}, any, {}> | ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions>undefinedestimateSize?(item: T, index: number) => number—minColumnWidth?number208maxColumns?number4gap?number12overscan?number4prefetch?number3animateItems?booleantrueariaLabel?string"Infinite masonry feed"contentClassName?string—itemClassName?string—emptyState?boolean | null | string | number | void | VNode<RendererNode, RendererElement, { [key: string]: any; }> | VNodeArrayChildren | ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> | FunctionalComponent<any, {}, any, {}> | ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions>—endState?boolean | null | string | number | void | VNode<RendererNode, RendererElement, { [key: string]: any; }> | VNodeArrayChildren | ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> | FunctionalComponent<any, {}, any, {}> | ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions>—事件
retry[]@retry插槽
empty() => VNodeChild#emptyloading—作用域参数: index
#loadingdefault—作用域参数: item, index
#defaultend() => VNodeChild#end无障碍
为交互控件提供明确的无障碍名称。保留文档中的键盘路径、可见焦点与减少动态效果状态;自定义插槽应继续保留所替代部件的语义。
常见问题
- 如何配置 Infinite Masonry?
- 通过 items、getItemKey、hasMore、onLoadMore、loading、error、estimateSize、minColumnWidth、maxColumns、gap、overscan、prefetch、animateItems、ariaLabel、contentClassName、itemClassName、emptyState、endState 配置 Infinite Masonry;API 表列出其类型与默认值。
- 如何连接到应用状态?
- 监听 retry,将组件连接到应用状态。
- 可以在服务端渲染吗?
- 可以在 Vue SSR 应用中正常渲染。服务端与客户端的初始 props 应保持一致;浏览器测量与交互资源在挂载后启动。
请留意
部分组件参考了开源作品,相关署名与独立许可请参阅 NOTICE。
更新于
来源与许可 ↗MIT · 0.1.1