Skip to content

Commit

Permalink
feat(cdk:scroll): virtual scroll supports horizontal
Browse files Browse the repository at this point in the history
BREAKING CHANGE: itemHeight is deprecated, use rowHeight instead
BREAKING CHANGE: itemRender is deprecated, use rowRender instead
  • Loading branch information
sallerli1 committed Dec 22, 2023
1 parent abbe62d commit a1dea88
Show file tree
Hide file tree
Showing 32 changed files with 1,227 additions and 214 deletions.
Empty file modified .husky/commit-msg
100755 → 100644
Empty file.
Empty file modified .husky/pre-commit
100755 → 100644
Empty file.
Empty file modified packages/cdk/platform/src/platform.ts
100755 → 100644
Empty file.
6 changes: 4 additions & 2 deletions packages/cdk/scroll/demo/Basic.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
ref="listRef"
:dataSource="data"
:height="200"
:itemHeight="20"
:rowHeight="20"
:bufferSize="20"
:bufferOffset="5"
getKey="key"
@scroll="onScroll"
@scrolledChange="onScrolledChange"
@scrolledBottom="onScrolledBottom"
>
<template #item="{ item, index }">
<template #row="{ item, index }">
<span class="virtual-item" @click="onItemClick(item.key)">{{ item.key }} - {{ index }}</span>
</template>
</CdkVirtualScroll>
Expand Down
14 changes: 14 additions & 0 deletions packages/cdk/scroll/demo/Both.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
title:
zh: 横向&竖向虚拟滚动
en: Horizontal & vertical virtual scroll
order: 3
---

## zh

开启横向以及竖向虚拟滚动的场景。

## en

Use horizontal and vertical virtual scroll.
79 changes: 79 additions & 0 deletions packages/cdk/scroll/demo/Both.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<template>
<div class="demo-both-scroll-wrapper">
<CdkVirtualScroll
ref="listRef"
:dataSource="rowData"
:height="200"
:rowHeight="20"
:colWidth="200"
:bufferSize="5"
:bufferOffset="2"
:enabled="true"
:rowRender="rowRender"
getKey="key"
>
<template #col="{ row, item, index }">
<span class="virtual-item" @click="onItemClick(item.key)">{{ row.key }} - {{ index }}</span>
</template>
</CdkVirtualScroll>
</div>
</template>

<script setup lang="ts">
import { h, ref } from 'vue'
import { VirtualRowRenderFn, VirtualScrollInstance, VirtualScrollRowData } from '@idux/cdk/scroll'
const listRef = ref<VirtualScrollInstance>()
const colData: { key: string }[] = []
for (let index = 0; index < 1000; index++) {
colData.push({ key: `col-key-${index}` })
}
const rowData: VirtualScrollRowData[] = []
for (let index = 0; index < 1000; index++) {
rowData.push({
key: `row-key-${index}`,
data: colData,
})
}
const rowRender: VirtualRowRenderFn = ({ children }) =>
h(
'div',
{
class: 'virtual-row',
},
children,
)
const onItemClick = (key: string) => {
console.log('click:', key)
}
</script>

<style lang="less">
.demo-both-scroll-wrapper {
height: 240px;
.cdk-virtual-scroll {
border: 1px solid red;
margin-bottom: 8px;
}
.virtual-row {
flex-shrink: 0;
display: flex;
height: 20px;
flex-wrap: nowrap;
border: 1px solid gray;
}
.virtual-item {
flex-shrink: 0;
padding-left: 16px;
border: 1px solid gray;
height: 100%;
width: 200px;
line-height: 18px;
}
}
</style>
14 changes: 14 additions & 0 deletions packages/cdk/scroll/demo/Horizontal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
title:
zh: 横向虚拟滚动
en: Horizontal virtual scroll
order: 2
---

## zh

仅开启横向虚拟滚动的场景。

## en

Only use horizontal virtual scroll.
93 changes: 93 additions & 0 deletions packages/cdk/scroll/demo/Horizontal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<template>
<div class="demo-horizontal-scroll-wrapper">
<CdkVirtualScroll
ref="listRef"
:dataSource="rowData"
:height="200"
:fullHeight="true"
:colWidth="80"
:bufferSize="20"
:bufferOffset="5"
:enabled="{ vertical: false, horizontal: true }"
:rowRender="rowRender"
getKey="key"
@scroll="onScroll"
@scrolledChange="onScrolledChange"
@scrolledBottom="onScrolledBottom"
>
<template #col="{ item, index }">
<span class="virtual-item" @click="onItemClick(item.key)">{{ item.key }} - {{ index }}</span>
</template>
</CdkVirtualScroll>
</div>
</template>

<script setup lang="ts">
import { h, ref } from 'vue'
import { VirtualRowRenderFn, VirtualScrollInstance } from '@idux/cdk/scroll'
const listRef = ref<VirtualScrollInstance>()
const data: { key: string }[] = []
for (let index = 0; index < 1000; index++) {
data.push({ key: `key-${index}` })
}
const rowData = [
{
key: 'row',
data,
},
]
const rowRender: VirtualRowRenderFn = ({ children }) =>
h(
'div',
{
class: 'virtual-row',
style: { display: 'flex', height: '100%', 'flex-wrap': 'nowrap' },
},
children,
)
const onScroll = (evt: Event) => {
console.log('scroll:', evt)
}
const onScrolledChange = (startIndex: number, endIndex: number, visibleData: { key: string }[]) =>
console.log('onScrolledChange', startIndex, endIndex, visibleData)
const onScrolledBottom = () => console.log('onScrolledBottom')
const onItemClick = (key: string) => {
console.log('click:', key)
}
</script>

<style lang="less">
.demo-horizontal-scroll-wrapper {
height: 240px;
.cdk-virtual-scroll {
border: 1px solid red;
margin-bottom: 8px;
}
.cdk-virtual-scroll-content {
height: 100%;
}
.virtual-row {
display: flex;
height: 100%;
flex-wrap: nowrap;
}
.virtual-item {
flex-shrink: 0;
padding-left: 16px;
border: 1px solid gray;
height: 100%;
width: 80px;
line-height: 18px;
}
}
</style>
6 changes: 3 additions & 3 deletions packages/cdk/scroll/demo/Switch.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<CdkVirtualScroll :dataSource="data" :height="height" getKey="key" :itemHeight="20" :itemRender="itemRender">
<CdkVirtualScroll :dataSource="data" :height="height" getKey="key" :rowHeight="20" :rowRender="itemRender">
</CdkVirtualScroll>

<IxSpace>
Expand Down Expand Up @@ -33,7 +33,7 @@
<script setup lang="ts">
import { computed, h, ref } from 'vue'
import { VirtualItemRenderFn } from '@idux/cdk/scroll'
import { VirtualRowRenderFn } from '@idux/cdk/scroll'
const getData = (length: number, key = 'key') => {
const data: { key: string }[] = []
Expand All @@ -47,7 +47,7 @@ const dataLength = ref(20)
const data = computed(() => getData(dataLength.value))
const height = ref(200)
const switchItemRender = ref(false)
const itemRender = computed<VirtualItemRenderFn>(() => {
const itemRender = computed<VirtualRowRenderFn>(() => {
if (switchItemRender.value) {
return ({ item }) => h('div', { style: { height: '20px', paddingLeft: '8px' } }, [`${item.key}`])
} else {
Expand Down
58 changes: 53 additions & 5 deletions packages/cdk/scroll/docs/Api.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,66 @@
| --- | --- | --- | --- | --- | --- |
| `contentRender` | 自定义列表的容器节点 | `VirtualContentRenderFn \| #content={children}` | - | - | - |
| `dataSource` | 需要渲染的数据列表 | `Array` | `[]` | - | - |
| `fullHeight` | 是否永远使用 `height` 作为容器高度 | `boolean` | `false` | - | 仅在不符合虚拟滚动条件时生效 |
| `getKey` | 列表项的唯一标识 | `string \| (item) => VKey` | `key` | - | - |
| `height` | 列表的高度 | `number` | `0` | - | 设置为大于 0 时才可以启用虚拟滚动 |
| `itemHeight` | 列表项的高度 | `number` | `0` | - | 设置为大于 0 时才可以启用虚拟滚动 |
| `itemRender` | 列表项的渲染函数 | `VirtualItemRenderFn \| #item={item, index}` | - | - | 必须设置或者提供 `item` 插槽 |
| `virtual` | 是否启用虚拟滚动 | `boolean` | `true` | - | - |
| `height` | 列表的高度 | `number \| 'auto' \| '100%'` | `0` | - | 设置为大于 0 时才可以启用虚拟滚动 |
| `fullHeight` | 是否永远使用 `height` 作为容器高度 | `boolean` | `false` | - | 仅在不符合虚拟滚动条件时生效 |
| `width` | 列表的宽度 | `number \| 100%` | `0` | - | 设置为大于 0 时才可以启用虚拟滚动 |
| `fullHeight` | 是否永远使用 `width` 作为容器的宽度 | `boolean` | `false` | - | 仅在不符合虚拟滚动条件时生效 |
| `rowHeight` | 列表行的高度 | `number` | `0` | - | 设置为大于 0 时才可以启用虚拟滚动 |
| `colWidth` | 列表列的宽度 | `number` | `0` | - | 设置为大于 0 时才可以启用虚拟滚动 |
| `rowRender` | 列表行的渲染函数 | `VirtualRowRenderFn \| #row={item, index}` | - | - | 必须设置或者提供 `row` 插槽 |
| `colRender` | 列表列的渲染函数 | `VirtualColRenderFn \| #col={row, item, index}` | - | - | 必须设置或者提供 `col` 插槽 |
| `virtual` | 是否启用虚拟滚动 | `boolean \| VirtualScrollEnabled` | `{ horizontal: true, vertical: false }` | - | - |
| `buffer` | 缓冲区大小 | `numnber` | `0` | - | - |
| `bufferOffset` | 在距离数据边界有几项时开始渲染下一屏数据 | `numnber` | `0` | - | - |
| `isStrictGrid` | 是否是严格的栅格 | `boolean` | `true` | - | 严格的栅格指行列之前是对齐的,即上一行的某列和下一行的这一列一定是对齐的 |
| `onScroll` | 滚动事件 | `(evt: Event) => void` | - | - | - |
| `onScrolledChange` | 滚动的位置发生变化 | `(startIndex: number, endIndex: number, visibleData: any[]) => void` | - | - | - |
| `onScrolledBottom` | 滚动到底部时触发 | `() => void` | - | - | - |

```ts
type VirtualRowRenderFn<Row = any> = (option: { item: Row; index: number; children?: VNode[] }) => VNodeChild

type VirtualColRenderFn<Row = any, Col = any> = (option: { row: Row; item: Col; index: number }) => VNodeChild

type VirtualContentRenderFn = (children: VNode[]) => VNodeChild

type VirtualScrollEnabled = { horizontal: boolean; vertical: boolean }
```
#### VirtualScrollMethods
| 名称 | 说明 | 参数类型 | 备注 |
| --- | --- | --- | --- |
| `scrollTo` | 手动设置滚动条位置 | `(value?: number \| VirtualScrollToOptions) => void` | 支持滚动到具体的 key 或者 index, 以及设置偏移量 |
#### 如何开启横向虚拟滚动
##### 设置virtual
设置 `virtual``true` 或者 `{ horizontal: true }`
##### 调整数据结构
由于所有的行和列必须都有key来优化渲染,因此数据不可以只是一个二维数组,需要是以下的数据结构组成的数组:
```ts
interface RowData {
[key: string]: any
data: unknown[]
}
```

其中 `RowData` 是行数据,其中的 `data` 是列数据

##### 根据实际情况设置 `isStrictGrid`

我们允许每一行的列都是独立的宽度,即每一行的列数和列宽是不一样的,但这样会极大增加计算复杂度。

因此,在每行的列都对齐,即每行的列都一样宽的情况下,只需要计算一行即可,在 `isStrictGrid``true` 时,会采取这种计算策略。

##### 根据渲染复杂度确定是否开启 `buffer``bufferOffset`

在开启了 `buffer` 后,会在当前窗口可渲染的行列基础上,再多渲染一些数据来缓解渲染频率过高的问题,但这在数据的渲染过于复杂的情况下并不适用,因为单次渲染的节点会增加,从而会导致渲染卡顿。

`bufferOffset` 的意义,是在滚动到距离所有渲染的项目边界还有几项时就提前渲染下一屏的数据,它可以一定程度上让滚动更加顺滑,但是也增加了渲染频率。
5 changes: 4 additions & 1 deletion packages/cdk/scroll/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ export type {
VirtualScrollComponent,
VirtualScrollPublicProps as VirtualScrollProps,
VirtualContentRenderFn,
VirtualItemRenderFn,
VirtualRowRenderFn,
VirtualColRenderFn,
VirtualScrollToOptions,
VirtualScrollToFn,
VirtualScrollEnabled,
VirtualScrollRowData,
} from './src/virtual/types'

0 comments on commit a1dea88

Please sign in to comment.