快速开始
@silver-formily/grid 用于根据容器尺寸和子节点 span 动态计算网格布局。
安装
bash
pnpm add @silver-formily/grid @formily/reactivebash
npm install @silver-formily/grid @formily/reactive基础用法
ts
import { Grid } from '@silver-formily/grid'
const grid = new Grid({
minColumns: 2,
maxColumns: 4,
minWidth: 120,
maxWidth: 220,
})
const dispose = grid.connect(container)<script setup lang="ts">
import { Grid } from '@silver-formily/grid'
import { onBeforeUnmount, onMounted, ref } from 'vue'
const containerRef = ref<HTMLElement | null>(null)
const templateColumns = ref('repeat(1,minmax(0,1fr))')
const gap = ref('8px 8px')
const items = [
{ title: 'A', span: 2 },
{ title: 'B', span: 1 },
{ title: 'C', span: 1 },
{ title: 'D', span: 3 },
{ title: 'E', span: 1 },
{ title: 'F', span: 2 },
]
let dispose: (() => void) | undefined
const grid = new Grid({
minColumns: 2,
maxColumns: 4,
minWidth: 120,
maxWidth: 220,
columnGap: 12,
rowGap: 12,
onInitialized(current) {
templateColumns.value = current.templateColumns
gap.value = current.gap
},
onDigest(current) {
templateColumns.value = current.templateColumns
gap.value = current.gap
},
})
onMounted(() => {
if (!containerRef.value)
return
dispose = grid.connect(containerRef.value)
})
onBeforeUnmount(() => {
dispose?.()
})
</script>
<template>
<div>
<div ref="containerRef" class="grid-board" :style="{ gridTemplateColumns: templateColumns, gap }">
<div
v-for="(item, index) in items"
:key="item.title"
class="grid-card"
:data-grid-span="item.span"
>
<strong>{{ item.title }}</strong>
<span>span {{ item.span }}</span>
<small>#{{ index + 1 }}</small>
</div>
</div>
</div>
</template>
<style scoped>
.grid-board {
display: grid;
}
.grid-card {
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
padding: 12px;
background: var(--vp-c-bg-soft);
display: flex;
flex-direction: column;
gap: 6px;
}
</style>Aspan 2#1
Bspan 1#2
Cspan 1#3
Dspan 3#4
Espan 1#5
Fspan 2#6
查看源码
Vue 示例
展示在 Vue 中推荐的接入方式:
- 直接
new Grid(...),实例已默认跳过 Vue 深响应式代理。 - 通过
watch更新grid.options,让布局规则与表单状态联动。
<script setup lang="ts">
import { Grid } from '@silver-formily/grid'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
const containerRef = ref<HTMLElement | null>(null)
const compactMode = ref(false)
const hideSpan2 = ref(false)
const visibleCount = ref(0)
const templateColumns = ref('repeat(2,minmax(0,1fr))')
const gap = ref('10px 10px')
const items = [
{ title: 'Alpha', span: 2 },
{ title: 'Beta', span: 1 },
{ title: 'Gamma', span: 1 },
{ title: 'Delta', span: 2 },
{ title: 'Epsilon', span: 1 },
{ title: 'Zeta', span: 1 },
]
let dispose: (() => void) | undefined
const createVisibleRule = () => (node: { originSpan: number }) => (hideSpan2.value ? node.originSpan !== 2 : true)
const grid = new Grid({
minColumns: 2,
maxColumns: 4,
minWidth: 140,
maxWidth: 260,
columnGap: 10,
rowGap: 10,
shouldVisible: createVisibleRule(),
onDigest(current) {
templateColumns.value = current.templateColumns
gap.value = current.gap
visibleCount.value = current.children.filter(node => node.visible).length
},
})
watch(compactMode, (enabled) => {
grid.options.columnGap = enabled ? 6 : 10
grid.options.rowGap = enabled ? 6 : 10
grid.options.maxWidth = enabled ? 220 : 260
})
watch(hideSpan2, () => {
grid.options.shouldVisible = createVisibleRule()
})
onMounted(() => {
if (!containerRef.value)
return
dispose = grid.connect(containerRef.value)
})
onBeforeUnmount(() => {
dispose?.()
})
</script>
<template>
<div>
<p>
<label><input v-model="compactMode" type="checkbox"> compactMode</label>
<label style="margin-left: 12px;"><input v-model="hideSpan2" type="checkbox"> hide span=2</label>
</p>
<p>
visible={{ visibleCount }}/{{ items.length }}
</p>
<div ref="containerRef" class="grid-board" :style="{ gridTemplateColumns: templateColumns, gap }">
<div
v-for="item in items"
:key="item.title"
class="grid-card"
:data-grid-span="item.span"
>
<strong>{{ item.title }}</strong>
<span>span {{ item.span }}</span>
</div>
</div>
</div>
</template>
<style scoped>
.grid-board {
display: grid;
}
.grid-card {
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
padding: 10px;
background: var(--vp-c-bg-soft);
}
</style>visible=0/6
Alphaspan 2
Betaspan 1
Gammaspan 1
Deltaspan 2
Epsilonspan 1
Zetaspan 1
查看源码
重大改动
Grid在构造时会自动标记为 raw(内置 Vue__v_skip),通常不需要再手动markRaw。在重构时去掉了polyfill的ResizeObserver,现在依赖浏览器原生的ResizeObserver,在使用时需要注意浏览器的版本。
本库略微添加了一些对SSR的支持,但算不上完善,因为从定位上来说这就是一个运行时才会获取与浏览器DOM绑定的库,其核心的功能也是在运行时计算正确的节点。
SSR指南
目前对SSR的处理是在SSR阶段视为容器宽度无限大的容器。因为SSR阶段无法获取用户实际的浏览器宽度,也无法计算真实的断点应该是哪个,想要兼容SSR的唯一办法是该网格布局不再使用断点,不然总是无法避免浏览器端与服务器端的渲染不一致,最好将其作为一个仅客户端渲染的库。