@pdanpdan/virtual-scroll 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +246 -297
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +873 -257
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2209 -1109
- package/dist/index.mjs.map +1 -1
- package/dist/virtual-scroll.css +1 -2
- package/package.json +5 -1
- package/src/components/VirtualScroll.test.ts +1886 -326
- package/src/components/VirtualScroll.vue +813 -340
- package/src/components/VirtualScrollbar.test.ts +174 -0
- package/src/components/VirtualScrollbar.vue +102 -0
- package/src/composables/useVirtualScroll.test.ts +1506 -228
- package/src/composables/useVirtualScroll.ts +789 -373
- package/src/composables/useVirtualScrollbar.test.ts +526 -0
- package/src/composables/useVirtualScrollbar.ts +239 -0
- package/src/index.ts +2 -0
- package/src/types.ts +333 -52
- package/src/utils/fenwick-tree.test.ts +39 -39
- package/src/utils/scroll.test.ts +133 -107
- package/src/utils/scroll.ts +12 -5
- package/src/utils/virtual-scroll-logic.test.ts +653 -320
- package/src/utils/virtual-scroll-logic.ts +685 -389
package/README.md
CHANGED
|
@@ -2,15 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
A high-performance, flexible virtual scrolling component for Vue 3.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Scaled Virtual Scroll
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
To support massive datasets (billions of pixels) while staying within browser scroll limits, the library uses a dual-unit coordinate system:
|
|
8
|
+
|
|
9
|
+
* **VU (Virtual Units)**: The internal coordinate system representing the actual size of your content.
|
|
10
|
+
* **DU (Display Units)**: The browser's physical coordinate system (limited to `BROWSER_MAX_SIZE`).
|
|
11
|
+
|
|
12
|
+
The library automatically calculates a scaling factor and applies a specialized formula to ensure **1:1 movement** in the viewport during wheel and touch scrolling, while maintaining proportional positioning during scrollbar interaction.
|
|
13
|
+
|
|
14
|
+
### Core Rendering Rule
|
|
15
|
+
Items are rendered at their VU size and positioned using `translateY()` based on the current display scroll position and their virtual offset. This prevents "jumping" and maintains sub-pixel precision even at extreme scales.
|
|
16
|
+
- **Virtual Scrollbars**: Highly optimized virtual scrollbars that replace native ones, providing consistent cross-browser styling and handling massive content scales. Supports custom slots for complete UI control.
|
|
17
|
+
- **Performance Optimized**: Uses CSS `@layer` for style isolation and `contain: layout` for improved rendering performance.
|
|
8
18
|
- **Dynamic & Fixed Sizes**: Supports both uniform item sizes and variable sizes via `ResizeObserver`.
|
|
9
19
|
- **Multi-Directional**: Works in `vertical`, `horizontal`, or `both` (grid) directions.
|
|
10
20
|
- **Container Flexibility**: Can use a custom element or the browser `window`/`body` as the scroll container.
|
|
11
21
|
- **SSR Support**: Built-in support for pre-rendering specific ranges for Server-Side Rendering.
|
|
12
22
|
- **Feature Rich**: Supports infinite scroll, loading states, sticky sections, headers, footers, buffers, and programmatic scrolling.
|
|
13
23
|
- **Scroll Restoration**: Automatically maintains scroll position when items are prepended to the list.
|
|
24
|
+
- **RTL Support**: Full support for Right-to-Left (RTL) layouts with automatic detection.
|
|
14
25
|
|
|
15
26
|
## Installation
|
|
16
27
|
|
|
@@ -36,11 +47,6 @@ import '@pdanpdan/virtual-scroll/style.css';
|
|
|
36
47
|
</script>
|
|
37
48
|
```
|
|
38
49
|
|
|
39
|
-
**Why use this?**
|
|
40
|
-
- Fastest build times (no need to compile the component logic).
|
|
41
|
-
- Maximum compatibility with different build tools.
|
|
42
|
-
- Scoped CSS works perfectly as it is extracted into `style.css` with unique data attributes.
|
|
43
|
-
|
|
44
50
|
### 2. Original Vue SFC
|
|
45
51
|
|
|
46
52
|
If you want to compile the component yourself using your own Vue compiler configuration, you can import the raw `.vue` file.
|
|
@@ -52,48 +58,15 @@ import VirtualScroll from '@pdanpdan/virtual-scroll/VirtualScroll.vue';
|
|
|
52
58
|
</script>
|
|
53
59
|
```
|
|
54
60
|
|
|
55
|
-
**Why use this?**
|
|
56
|
-
- Allows for better tree-shaking and optimization by your own bundler.
|
|
57
|
-
- Enables deep integration with your project's CSS-in-JS or specialized styling solutions.
|
|
58
|
-
- Easier debugging of the component source in some IDEs.
|
|
59
|
-
|
|
60
61
|
### 3. CDN Usage
|
|
61
62
|
|
|
62
|
-
You can use the library directly from a CDN like unpkg or jsdelivr.
|
|
63
|
-
|
|
64
63
|
```html
|
|
65
64
|
<!-- Import Vue 3 first -->
|
|
66
65
|
<script src="https://unpkg.com/vue@3"></script>
|
|
67
|
-
|
|
68
66
|
<!-- Import VirtualScroll CSS -->
|
|
69
|
-
<link rel="stylesheet" href="https://unpkg.com/@pdanpdan/virtual-scroll/dist/
|
|
70
|
-
|
|
67
|
+
<link rel="stylesheet" href="https://unpkg.com/@pdanpdan/virtual-scroll/dist/style.css">
|
|
71
68
|
<!-- Import VirtualScroll JavaScript -->
|
|
72
69
|
<script src="https://unpkg.com/@pdanpdan/virtual-scroll"></script>
|
|
73
|
-
|
|
74
|
-
<div id="app">
|
|
75
|
-
<div style="height: 400px; overflow: auto;">
|
|
76
|
-
<virtual-scroll :items="items" :item-size="50">
|
|
77
|
-
<template #item="{ item, index }">
|
|
78
|
-
<div style="height: 50px;">{{ index }}: {{ item.label }}</div>
|
|
79
|
-
</template>
|
|
80
|
-
</virtual-scroll>
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
<script>
|
|
85
|
-
const { createApp, ref } = Vue;
|
|
86
|
-
const { VirtualScroll } = window.VirtualScroll;
|
|
87
|
-
|
|
88
|
-
createApp({
|
|
89
|
-
setup() {
|
|
90
|
-
const items = ref(Array.from({ length: 1000 }, (_, i) => ({ label: `Item ${i}` })));
|
|
91
|
-
return { items };
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
.component('VirtualScroll', VirtualScroll)
|
|
95
|
-
.mount('#app');
|
|
96
|
-
</script>
|
|
97
70
|
```
|
|
98
71
|
|
|
99
72
|
## Basic Usage
|
|
@@ -108,322 +81,298 @@ const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, label: `Item ${
|
|
|
108
81
|
</script>
|
|
109
82
|
|
|
110
83
|
<template>
|
|
111
|
-
<
|
|
112
|
-
<
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
</div>
|
|
117
|
-
</template>
|
|
118
|
-
</VirtualScroll>
|
|
119
|
-
</div>
|
|
84
|
+
<VirtualScroll :items="items" :item-size="50" class="my-container">
|
|
85
|
+
<template #item="{ item, index }">
|
|
86
|
+
<div class="my-item">{{ index }}: {{ item.label }}</div>
|
|
87
|
+
</template>
|
|
88
|
+
</VirtualScroll>
|
|
120
89
|
</template>
|
|
121
90
|
|
|
122
91
|
<style>
|
|
123
|
-
.my-container {
|
|
124
|
-
|
|
125
|
-
overflow: auto;
|
|
126
|
-
}
|
|
127
|
-
.my-item {
|
|
128
|
-
height: 50px;
|
|
129
|
-
}
|
|
92
|
+
.my-container { height: 500px; }
|
|
93
|
+
.my-item { height: 50px; }
|
|
130
94
|
</style>
|
|
131
95
|
```
|
|
132
96
|
|
|
133
97
|
## Sizing Guide
|
|
134
98
|
|
|
135
|
-
The component offers flexible ways to define item and column sizes. Understanding how these options interact is key to achieving smooth scrolling and correct layout.
|
|
136
|
-
|
|
137
|
-
### Sizing Options
|
|
138
|
-
|
|
139
99
|
| Option Type | `itemSize` / `columnWidth` | Performance | Description |
|
|
140
100
|
|-------------|----------------------------|-------------|-------------|
|
|
141
101
|
| **Fixed** | `number` (e.g., `50`) | **Best** | Every item has the exact same size. Calculations are *O(1)*. |
|
|
142
102
|
| **Array** | `number[]` (cols only) | **Great** | Each column has a fixed size from the array (cycles if shorter). |
|
|
143
|
-
| **Function** | `(item, index) => number` | **Good** | Size is known but varies per item.
|
|
144
|
-
| **Dynamic** | `0`, `null`, or `undefined` | **Fair** | Sizes are measured automatically via `ResizeObserver
|
|
145
|
-
|
|
146
|
-
### How Sizing Works
|
|
147
|
-
|
|
148
|
-
1. **Initial Estimate**:
|
|
149
|
-
- If a **fixed size** or **function** is provided, it's used as the initial size.
|
|
150
|
-
- If **dynamic** is used, the component uses `defaultItemSize` (default: `40`) or `defaultColumnWidth` (default: `100`) as the initial estimate.
|
|
151
|
-
2. **Measurement**:
|
|
152
|
-
- When an item is rendered, its actual size is measured using `ResizeObserver`.
|
|
153
|
-
- If the measured size differs from the estimate (by more than 0.5px), the internal Fenwick Tree is updated.
|
|
154
|
-
3. **Refinement**:
|
|
155
|
-
- All subsequent item positions are automatically adjusted based on the new measurement.
|
|
156
|
-
- The total scrollable area (`totalWidth`/`totalHeight`) is updated to reflect the real content size.
|
|
103
|
+
| **Function** | `(item, index) => number` | **Good** | Size is known but varies per item. |
|
|
104
|
+
| **Dynamic** | `0`, `null`, or `undefined` | **Fair** | Sizes are measured automatically via `ResizeObserver`. |
|
|
157
105
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
- **Unset Props**: If `itemSize` or `columnWidth` are not provided, they default to `40` and `100` respectively (fixed).
|
|
161
|
-
- **Dynamic Fallback**: When using dynamic sizing, `defaultItemSize` and `defaultColumnWidth` act as the source of truth for items that haven't been rendered yet.
|
|
162
|
-
- **Function/Array Fallback**: If a function or array returns an invalid value, it falls back to the respective `default...` prop.
|
|
163
|
-
|
|
164
|
-
### Recommendations for Smooth Scrolling
|
|
165
|
-
|
|
166
|
-
1. **Accurate Estimates**: When using dynamic sizing, set `defaultItemSize` as close as possible to the *average* height of your items. This minimizes scrollbar "jumping".
|
|
167
|
-
2. **Avoid 0 sizes**: Ensure your items have a minimum height/width (e.g., via CSS `min-height`). Items with 0 size might not be detected correctly by the virtualizer.
|
|
168
|
-
3. **Box Sizing**: Use `box-sizing: border-box` on your items to ensure padding and borders are included in the measured size.
|
|
169
|
-
4. **Manual Refresh**: If you change external state that affects a sizing function's output without changing the function reference itself, call `virtualScrollRef.refresh()` to force a full re-calculation.
|
|
170
|
-
|
|
171
|
-
## Component Reference
|
|
172
|
-
|
|
173
|
-
The `VirtualScroll` component provides a declarative interface for virtualizing lists and grids. It automatically manages the rendering lifecycle of items, measures dynamic sizes, and handles complex scroll behaviors like stickiness and restoration.
|
|
106
|
+
## Component Reference: VirtualScroll
|
|
174
107
|
|
|
175
108
|
### Props
|
|
176
109
|
|
|
177
|
-
#### Core Configuration
|
|
178
110
|
| Prop | Type | Default | Description |
|
|
179
111
|
|------|------|---------|-------------|
|
|
180
112
|
| `items` | `T[]` | Required | Array of items to be virtualized. |
|
|
181
|
-
| `itemSize` | `number \|
|
|
113
|
+
| `itemSize` | `number \| fn \| null` | `40` | Fixed size or function. Pass `0`/`null` for dynamic. |
|
|
182
114
|
| `direction` | `'vertical' \| 'horizontal' \| 'both'` | `'vertical'` | Scroll direction. |
|
|
183
|
-
| `gap` | `number` | `0` | Spacing between items. |
|
|
184
|
-
|
|
185
|
-
#### Grid Configuration (only for direction="both")
|
|
186
|
-
| Prop | Type | Default | Description |
|
|
187
|
-
|------|------|---------|-------------|
|
|
188
115
|
| `columnCount` | `number` | `0` | Number of columns for grid mode. |
|
|
189
116
|
| `columnWidth` | `num \| num[] \| fn \| null` | `100` | Width for columns in grid mode. |
|
|
190
|
-
| `columnGap` | `number` | `0` | Spacing between columns. |
|
|
191
|
-
|
|
192
|
-
#### Features & Behavior
|
|
193
|
-
| Prop | Type | Default | Description |
|
|
194
|
-
|------|------|---------|-------------|
|
|
117
|
+
| `gap` / `columnGap` | `number` | `0` | Spacing between items/columns. |
|
|
195
118
|
| `stickyIndices` | `number[]` | `[]` | Indices of items that should remain sticky. |
|
|
196
119
|
| `stickyHeader` / `stickyFooter` | `boolean` | `false` | If true, measures and adds slot size to padding. |
|
|
197
|
-
| `
|
|
198
|
-
| `loading` | `boolean` | `false` | Shows loading state and prevents duplicate events. |
|
|
199
|
-
| `loadDistance` | `number` | `200` | Distance from end to trigger `load` event. |
|
|
120
|
+
| `virtualScrollbar` | `boolean` | `false` | Whether to force virtual scrollbars. |
|
|
200
121
|
| `restoreScrollOnPrepend` | `boolean` | `false` | Maintain position when items added to top. |
|
|
201
|
-
| `
|
|
202
|
-
| `initialScrollAlign` | `ScrollAlignment \| object` | `'start'` | Alignment for initial jump. |
|
|
203
|
-
|
|
204
|
-
#### Advanced & Performance
|
|
205
|
-
| Prop | Type | Default | Description |
|
|
206
|
-
|------|------|---------|-------------|
|
|
207
|
-
| `container` | `HTMLElement \| Window` | `host element` | The scrollable container element. |
|
|
122
|
+
| `container` | `HTMLElement \| Window` | `hostRef` | The scrollable container element. |
|
|
208
123
|
| `scrollPaddingStart` / `End` | `num \| {x, y}` | `0` | Padding for scroll calculations. |
|
|
209
|
-
| `containerTag` / `wrapperTag` / `itemTag` | `string` | `'div'` | HTML tags for component parts. |
|
|
210
124
|
| `bufferBefore` / `bufferAfter` | `number` | `5` | Items to render outside the viewport. |
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
-
|
|
222
|
-
-
|
|
223
|
-
-
|
|
224
|
-
-
|
|
225
|
-
-
|
|
226
|
-
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
-
|
|
231
|
-
-
|
|
232
|
-
-
|
|
233
|
-
|
|
234
|
-
## Keyboard Navigation
|
|
235
|
-
|
|
236
|
-
When the container is focused (it has `tabindex="0"`), it supports:
|
|
237
|
-
- `Home`: Scroll to the very beginning (Index 0,0).
|
|
238
|
-
- `End`: Scroll to the very last row and column.
|
|
239
|
-
- `ArrowUp` / `ArrowDown`: Scroll up/down by 40px (or `DEFAULT_ITEM_SIZE`).
|
|
240
|
-
- `ArrowLeft` / `ArrowRight`: Scroll left/right by 40px (or `DEFAULT_ITEM_SIZE`).
|
|
241
|
-
- `PageUp` / `PageDown`: Scroll by one full viewport height/width.
|
|
242
|
-
|
|
243
|
-
### CSS Classes
|
|
244
|
-
|
|
245
|
-
- `.virtual-scroll-container`: Root container.
|
|
246
|
-
- `.virtual-scroll--vertical`, `.virtual-scroll--horizontal`, `.virtual-scroll--both`: Direction modifier.
|
|
247
|
-
- `.virtual-scroll-wrapper`: Items wrapper.
|
|
248
|
-
- `.virtual-scroll-item`: Individual item.
|
|
249
|
-
- `.virtual-scroll-header` / `.virtual-scroll-footer`: Header/Footer slots.
|
|
250
|
-
- `.virtual-scroll-loading`: Loading slot container.
|
|
251
|
-
- `.virtual-scroll--sticky`: Applied to sticky elements.
|
|
252
|
-
- `.virtual-scroll--hydrated`: Applied when client-side mount is complete.
|
|
253
|
-
- `.virtual-scroll--window`: Applied when using window/body scroll.
|
|
254
|
-
- `.virtual-scroll--table`: Applied when `containerTag="table"` is used.
|
|
255
|
-
- `.virtual-scroll--debug`: Applied when debug mode is enabled.
|
|
125
|
+
| `initialScrollIndex` | `number` | `undefined` | Index to jump to on mount. |
|
|
126
|
+
| `initialScrollAlign` | `ScrollAlignment \| object` | `'start'` | Alignment for initial jump. See [ScrollAlignment](#scrollalignment). |
|
|
127
|
+
| `defaultItemSize` / `defaultColumnWidth` | `number` | `40 / 100` | Estimate for dynamic items/columns. |
|
|
128
|
+
|
|
129
|
+
### Slots
|
|
130
|
+
|
|
131
|
+
- `item`: Scoped slot for individual items. Provides `item`, `index`, `columnRange`, `getColumnWidth`, `gap`, `columnGap`, `isSticky`, `isStickyActive`.
|
|
132
|
+
- `header` / `footer`: Content rendered at the top/bottom of the scrollable area.
|
|
133
|
+
- `loading`: Content shown at the end when `loading` prop is true.
|
|
134
|
+
- `scrollbar`: Scoped slot for custom scrollbar. Called once for each active axis.
|
|
135
|
+
- `axis`: `'vertical' | 'horizontal'`
|
|
136
|
+
- `positionPercent`: current position (0-1).
|
|
137
|
+
- `viewportPercent`: viewport percentage (0-1).
|
|
138
|
+
- `trackProps`: Attributes/listeners for the track. Bind with `v-bind="trackProps"`.
|
|
139
|
+
- `thumbProps`: Attributes/listeners for the thumb. Bind with `v-bind="thumbProps"`.
|
|
140
|
+
- `scrollbarProps`: Grouped props for the `VirtualScrollbar` component.
|
|
141
|
+
- `axis`: `'vertical' | 'horizontal'`
|
|
142
|
+
- `totalSize`: virtual content size in pixels.
|
|
143
|
+
- `position`: current virtual scroll offset.
|
|
144
|
+
- `viewportSize`: virtual visible area size.
|
|
145
|
+
- `scrollToOffset`: `(offset: number) => void`
|
|
146
|
+
- `containerId`: unique ID of the container.
|
|
147
|
+
- `isRtl`: `boolean` (current RTL state).
|
|
256
148
|
|
|
257
149
|
### Exposed Members
|
|
258
150
|
|
|
259
|
-
|
|
151
|
+
The following properties and methods are available on the `VirtualScroll` component instance (via template `ref`).
|
|
260
152
|
|
|
261
|
-
|
|
262
|
-
-
|
|
263
|
-
-
|
|
264
|
-
-
|
|
265
|
-
-
|
|
266
|
-
-
|
|
267
|
-
-
|
|
153
|
+
#### Properties
|
|
154
|
+
- **All Props**: All properties defined in [Props](#props) are available on the instance.
|
|
155
|
+
- [`scrollDetails`](#scrolldetails): Full reactive state of the virtual scroll system.
|
|
156
|
+
- [`columnRange`](#columnrange): Information about the current visible range of columns.
|
|
157
|
+
- `isHydrated`: `true` when the component is mounted and hydrated.
|
|
158
|
+
- `isRtl`: `true` if the container is in Right-to-Left mode.
|
|
159
|
+
- [`scrollbarPropsVertical`](#scrollbarslotprops) / [`scrollbarPropsHorizontal`](#scrollbarslotprops): Reactive [ScrollbarSlotProps](#scrollbarslotprops).
|
|
160
|
+
- `scaleX` / `scaleY`: Current coordinate scaling factors (VU/DU).
|
|
161
|
+
- `renderedWidth` / `renderedHeight`: Physical dimensions in DOM (clamped, DU).
|
|
162
|
+
- `componentOffset`: Absolute offset of the component within its container (DU).
|
|
268
163
|
|
|
269
|
-
|
|
164
|
+
#### Methods
|
|
165
|
+
- `scrollToIndex(row, col, options)`: Programmatic scroll to index.
|
|
166
|
+
- `scrollToOffset(x, y, options)`: Programmatic scroll to pixel position.
|
|
167
|
+
- `refresh()`: Resets all measurements and state.
|
|
168
|
+
- `stopProgrammaticScroll()`: Halt smooth scroll animations.
|
|
169
|
+
- `updateDirection()`: Manually trigger direction detection.
|
|
170
|
+
- `getRowHeight(index)`: Returns the calculated height of a row.
|
|
171
|
+
- `getColumnWidth(index)`: Returns the calculated width of a column.
|
|
172
|
+
- `getRowOffset(index)`: Returns the virtual offset of a row.
|
|
173
|
+
- `getColumnOffset(index)`: Returns the virtual offset of a column.
|
|
174
|
+
- `getItemOffset(index)`: Returns the virtual offset of an item.
|
|
175
|
+
- `getItemSize(index)`: Returns the size of an item along the scroll axis.
|
|
270
176
|
|
|
271
|
-
|
|
177
|
+
## Virtual Scrollbars
|
|
272
178
|
|
|
273
|
-
|
|
179
|
+
Virtual scrollbars are automatically enabled when content size exceeds browser limits, but can be forced via the `virtualScrollbar` prop.
|
|
274
180
|
|
|
275
|
-
|
|
276
|
-
/* eslint-disable unused-imports/no-unused-vars */
|
|
277
|
-
import { useVirtualScroll } from '@pdanpdan/virtual-scroll';
|
|
278
|
-
import { computed, ref } from 'vue';
|
|
181
|
+
**Note:** Virtual scrollbars and coordinate scaling are automatically disabled when the `container` is the browser `window` or `body`. In these cases, native scrolling behavior is used.
|
|
279
182
|
|
|
280
|
-
|
|
281
|
-
const containerRef = ref<HTMLElement | null>(null);
|
|
183
|
+
### Using the `VirtualScrollbar` Component
|
|
282
184
|
|
|
283
|
-
|
|
284
|
-
items: items.value,
|
|
285
|
-
itemSize: 50,
|
|
286
|
-
container: containerRef.value,
|
|
287
|
-
direction: 'vertical' as const,
|
|
288
|
-
}));
|
|
185
|
+
You can use the built-in `VirtualScrollbar` independently if needed.
|
|
289
186
|
|
|
290
|
-
|
|
291
|
-
|
|
187
|
+
```vue
|
|
188
|
+
<script setup>
|
|
189
|
+
import { VirtualScrollbar } from '@pdanpdan/virtual-scroll';
|
|
190
|
+
import { ref } from 'vue';
|
|
292
191
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
| Member | Type | Description |
|
|
298
|
-
|--------|------|-------------|
|
|
299
|
-
| `renderedItems` | `Ref<RenderedItem<T>[]>` | List of items to render in the buffer. |
|
|
300
|
-
| `scrollDetails` | `Ref<ScrollDetails<T>>` | Full reactive state of the virtualizer. |
|
|
301
|
-
| `totalWidth` / `totalHeight` | `Ref<number>` | Calculated total content dimensions. |
|
|
302
|
-
| `columnRange` | `Ref<ColumnRange>` | Visible column indices and paddings. |
|
|
303
|
-
| `isHydrated` | `Ref<boolean>` | True after mount and hydration. |
|
|
304
|
-
| `scrollToIndex` | `Function` | Programmatic scroll to row/column index. |
|
|
305
|
-
| `scrollToOffset` | `Function` | Programmatic scroll to pixel offset. |
|
|
306
|
-
| `stopProgrammaticScroll` | `Function` | Cancel any active smooth scroll animation. |
|
|
307
|
-
| `updateItemSize` | `Function` | Register a new manual item measurement. |
|
|
308
|
-
| `updateItemSizes` | `Function` | Register multiple manual item measurements. |
|
|
309
|
-
| `updateHostOffset` | `Function` | Recalculate host element position. |
|
|
310
|
-
| `getColumnWidth` | `Function` | Helper to get a column's width. |
|
|
311
|
-
| `refresh` | `Function` | Resets all measurements and state. |
|
|
192
|
+
const scrollX = ref(0);
|
|
193
|
+
const scrollY = ref(0);
|
|
194
|
+
</script>
|
|
312
195
|
|
|
313
|
-
|
|
196
|
+
<template>
|
|
197
|
+
<div class="my-container relative overflow-hidden">
|
|
198
|
+
<VirtualScrollbar
|
|
199
|
+
axis="vertical"
|
|
200
|
+
:total-size="10000"
|
|
201
|
+
:viewport-size="500"
|
|
202
|
+
:position="scrollY"
|
|
203
|
+
@scroll-to-offset="val => scrollY = val"
|
|
204
|
+
/>
|
|
205
|
+
<VirtualScrollbar
|
|
206
|
+
axis="horizontal"
|
|
207
|
+
:total-size="10000"
|
|
208
|
+
:viewport-size="800"
|
|
209
|
+
:position="scrollX"
|
|
210
|
+
@scroll-to-offset="val => scrollX = val"
|
|
211
|
+
/>
|
|
212
|
+
</div>
|
|
213
|
+
</template>
|
|
214
|
+
```
|
|
314
215
|
|
|
315
|
-
###
|
|
216
|
+
### Using the `scrollbar` Slot
|
|
316
217
|
|
|
317
|
-
|
|
318
|
-
| Property | Type | Description |
|
|
319
|
-
|----------|------|-------------|
|
|
320
|
-
| `items` | `RenderedItem<T>[]` | Rendered items in the buffer. |
|
|
321
|
-
| `currentIndex` | `number` | First visible row index. |
|
|
322
|
-
| `currentColIndex` | `number` | First visible column index. |
|
|
323
|
-
| `scrollOffset` | `{ x: number, y: number }` | Current pixel scroll position. |
|
|
324
|
-
| `viewportSize` | `{ width: number, height: number }` | Dimensions of the visible area. |
|
|
325
|
-
| `totalSize` | `{ width: number, height: number }` | Estimated total dimensions. |
|
|
326
|
-
| `isScrolling` | `boolean` | Active scrolling state. |
|
|
327
|
-
| `isProgrammaticScroll` | `boolean` | True if triggered by method. |
|
|
328
|
-
| `range` | `{ start: number, end: number }` | Visible row range. |
|
|
329
|
-
| `columnRange` | `ColumnRange` | Visible column range (grid). |
|
|
330
|
-
|
|
331
|
-
#### RenderedItem<T>
|
|
332
|
-
| Property | Type | Description |
|
|
333
|
-
|----------|------|-------------|
|
|
334
|
-
| `item` | `T` | The source data item. |
|
|
335
|
-
| `index` | `number` | Position in original array. |
|
|
336
|
-
| `offset` | `{ x: number, y: number }` | Position relative to wrapper. |
|
|
337
|
-
| `size` | `{ width, height }` | Current dimensions. |
|
|
338
|
-
| `originalX` / `originalY` | `number` | Offsets before sticky adjustments. |
|
|
339
|
-
| `isSticky` | `boolean` |configured as sticky. |
|
|
340
|
-
| `isStickyActive` | `boolean` | Currently stuck. |
|
|
341
|
-
| `stickyOffset` | `{ x, y }` | Translation for pushing effect. |
|
|
342
|
-
|
|
343
|
-
#### ColumnRange
|
|
344
|
-
| Property | Type | Description |
|
|
345
|
-
|----------|------|-------------|
|
|
346
|
-
| `start` | `number` | First rendered column index. |
|
|
347
|
-
| `end` | `number` | Last rendered column index (exclusive). |
|
|
348
|
-
| `padStart` | `number` | Pixel space before first column. |
|
|
349
|
-
| `padEnd` | `number` | Pixel space after last column. |
|
|
350
|
-
|
|
351
|
-
#### ScrollToIndexOptions
|
|
352
|
-
| Property | Type | Description |
|
|
353
|
-
|----------|------|-------------|
|
|
354
|
-
| `align` | `ScrollAlignment \| ScrollAlignmentOptions` | How to position the item (default: `'auto'`). |
|
|
355
|
-
| `behavior` | `'auto' \| 'smooth'` | Scroll animation behavior (default: `'auto'`). |
|
|
356
|
-
|
|
357
|
-
#### ScrollAlignmentOptions
|
|
358
|
-
| Property | Type | Description |
|
|
359
|
-
|----------|------|-------------|
|
|
360
|
-
| `x` | `ScrollAlignment` | Alignment on the horizontal axis. |
|
|
361
|
-
| `y` | `ScrollAlignment` | Alignment on the vertical axis. |
|
|
362
|
-
|
|
363
|
-
#### ScrollAlignment
|
|
364
|
-
Values: `'start' | 'center' | 'end' | 'auto'`
|
|
218
|
+
The `scrollbar` slot provides everything needed to build a fully custom interface using `v-bind`. It is called once for each active axis.
|
|
365
219
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
220
|
+
```vue
|
|
221
|
+
<template>
|
|
222
|
+
<VirtualScroll :items="items" direction="both" virtual-scrollbar>
|
|
223
|
+
<template #scrollbar="{ trackProps, thumbProps, axis }">
|
|
224
|
+
<!-- Handle axes separately -->
|
|
225
|
+
<div v-if="axis === 'vertical'" v-bind="trackProps" class="custom-v-track">
|
|
226
|
+
<div v-bind="thumbProps" class="custom-v-thumb" />
|
|
227
|
+
</div>
|
|
228
|
+
<div v-else v-bind="trackProps" class="custom-h-track">
|
|
229
|
+
<div v-bind="thumbProps" class="custom-h-thumb" />
|
|
230
|
+
</div>
|
|
231
|
+
</template>
|
|
232
|
+
</VirtualScroll>
|
|
233
|
+
</template>
|
|
234
|
+
```
|
|
370
235
|
|
|
371
|
-
###
|
|
236
|
+
### CSS Variables for Default Scrollbar
|
|
237
|
+
|
|
238
|
+
The default scrollbar uses CSS `@layer components` for better isolation and customization.
|
|
372
239
|
|
|
373
|
-
|
|
374
|
-
|
|
240
|
+
| Variable | Default (Light/Dark) | Description |
|
|
241
|
+
|----------|-----------------|-------------|
|
|
242
|
+
| `--vs-scrollbar-bg` | `rgba(230,230,230,0.9) / rgba(30,30,30,0.9)` | Track background color. |
|
|
243
|
+
| `--vs-scrollbar-thumb-bg` | `rgba(0,0,0,0.3) / rgba(255,255,255,0.3)` | Thumb background color. |
|
|
244
|
+
| `--vs-scrollbar-thumb-hover-bg` | `rgba(0,0,0,0.6) / rgba(255,255,255,0.6)` | Thumb background on hover/active. |
|
|
245
|
+
| `--vs-scrollbar-size` | `8px` | Width/height of the scrollbar. |
|
|
246
|
+
| `--vs-scrollbar-radius` | `4px` | Border radius for track and thumb. |
|
|
247
|
+
| `--vs-scrollbar-cross-gap` | `var(--vs-scrollbar-size)` | Size of gap to use where scrollbars meet. |
|
|
248
|
+
| `--vs-scrollbar-has-cross-gap` | `0` | If gap should be shown where scrollbars meet. |
|
|
375
249
|
|
|
376
|
-
|
|
377
|
-
Scrolls the container to an absolute pixel position.
|
|
250
|
+
## Composables
|
|
378
251
|
|
|
379
|
-
|
|
380
|
-
Immediately halts any active smooth scroll animation.
|
|
252
|
+
### `useVirtualScroll`
|
|
381
253
|
|
|
382
|
-
|
|
383
|
-
Manually registers a new measurement for a single item.
|
|
254
|
+
Provides the core logic for virtualization.
|
|
384
255
|
|
|
385
|
-
|
|
386
|
-
|
|
256
|
+
```ts
|
|
257
|
+
/* eslint-disable unused-imports/no-unused-vars, no-undef */
|
|
258
|
+
const { renderedItems, scrollDetails, scrollToIndex } = useVirtualScroll(props);
|
|
259
|
+
```
|
|
387
260
|
|
|
388
|
-
|
|
389
|
-
Forces a recalculation of the host element's position relative to the scroll container.
|
|
261
|
+
### `useVirtualScrollbar`
|
|
390
262
|
|
|
391
|
-
|
|
392
|
-
Returns the currently calculated width for a specific column index.
|
|
263
|
+
Provides the logic for scrollbar interactions.
|
|
393
264
|
|
|
394
|
-
|
|
395
|
-
|
|
265
|
+
```ts
|
|
266
|
+
/* eslint-disable unused-imports/no-unused-vars, no-undef */
|
|
267
|
+
const { trackProps, thumbProps } = useVirtualScrollbar(props);
|
|
268
|
+
```
|
|
396
269
|
|
|
397
270
|
## Utility Functions
|
|
398
271
|
|
|
399
|
-
|
|
272
|
+
- **Type Guards**:
|
|
273
|
+
- `isElement(val: any): val is HTMLElement`: Checks if value is a standard `HTMLElement` (excludes `window`).
|
|
274
|
+
- `isWindow(val: any): val is Window`: Checks for the global `window` object.
|
|
275
|
+
- `isBody(val: any): val is HTMLElement`: Checks for the `document.body` element.
|
|
276
|
+
- `isWindowLike(val: any): boolean`: Returns `true` if the value is `window` or `body`.
|
|
277
|
+
- `isScrollableElement(val: any): val is HTMLElement | Window`: Checks if value has scroll properties.
|
|
278
|
+
- `isScrollToIndexOptions(val: any): val is ScrollToIndexOptions`: Type guard for scroll options.
|
|
279
|
+
- `getPaddingX(p: number | object, dir: string): number`: Internal helper for padding.
|
|
280
|
+
- `getPaddingY(p: number | object, dir: string): number`: Internal helper for padding.
|
|
281
|
+
- **Coordinate Mapping**:
|
|
282
|
+
- `displayToVirtual(displayPos, hostOffset, scale): number`: Display pixels to virtual pixels.
|
|
283
|
+
- `virtualToDisplay(virtualPos, hostOffset, scale): number`: Virtual pixels to display pixels.
|
|
284
|
+
- `isItemVisible(itemPos, itemSize, scrollPos, viewSize, stickyOffset?): boolean`: Check item visibility.
|
|
285
|
+
- `FenwickTree`: Highly efficient data structure for size and offset management.
|
|
286
|
+
- **Default Constants**:
|
|
287
|
+
- `BROWSER_MAX_SIZE`: 10,000,000 (coordinate scaling threshold).
|
|
288
|
+
- `DEFAULT_ITEM_SIZE`: 40px (default row height).
|
|
289
|
+
- `DEFAULT_COLUMN_WIDTH`: 100px (default column width).
|
|
290
|
+
- `DEFAULT_BUFFER`: 5 items (default buffer before/after).
|
|
400
291
|
|
|
401
|
-
|
|
402
|
-
- `isScrollableElement(val: any): val is HTMLElement | Window`: Checks if value has scroll properties.
|
|
403
|
-
- `isScrollToIndexOptions(val: any): val is ScrollToIndexOptions`: Type guard for scroll options.
|
|
404
|
-
- `getPaddingX / getPaddingY`: Internal helpers for extraction of padding from props.
|
|
405
|
-
- `FenwickTree`: The underlying data structure for efficient size and offset management.
|
|
406
|
-
- `DEFAULT_ITEM_SIZE / DEFAULT_COLUMN_WIDTH / DEFAULT_BUFFER`: The default values used by the library.
|
|
292
|
+
## API Reference
|
|
407
293
|
|
|
408
|
-
|
|
294
|
+
### Types
|
|
409
295
|
|
|
410
|
-
|
|
296
|
+
#### `ScrollDirection`
|
|
297
|
+
Values: `'vertical' | 'horizontal' | 'both'`
|
|
411
298
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
299
|
+
#### `ScrollAxis`
|
|
300
|
+
Values: `'vertical' | 'horizontal'`
|
|
301
|
+
|
|
302
|
+
#### `ScrollAlignment`
|
|
303
|
+
Values: `'start' | 'center' | 'end' | 'auto'`
|
|
304
|
+
|
|
305
|
+
#### `ScrollToIndexOptions`
|
|
306
|
+
- `align`: `ScrollAlignment | ScrollAlignmentOptions`
|
|
307
|
+
- `behavior`: `'auto' | 'smooth'`
|
|
308
|
+
|
|
309
|
+
#### `ScrollAlignmentOptions`
|
|
310
|
+
- `x`: `ScrollAlignment`
|
|
311
|
+
- `y`: `ScrollAlignment`
|
|
312
|
+
|
|
313
|
+
#### `ScrollbarSlotProps`
|
|
314
|
+
- `positionPercent`: current position as a percentage (0 to 1).
|
|
315
|
+
- `viewportPercent`: viewport as a percentage of total size (0 to 1).
|
|
316
|
+
- `thumbSizePercent`: calculated thumb size as a percentage of the track (0 to 100).
|
|
317
|
+
- `thumbPositionPercent`: calculated thumb position as a percentage of the track (0 to 100).
|
|
318
|
+
- `trackProps`: attributes/listeners for track. Bind with `v-bind="trackProps"`.
|
|
319
|
+
- `thumbProps`: attributes/listeners for thumb. Bind with `v-bind="thumbProps"`.
|
|
320
|
+
- `scrollbarProps`: grouped props for `VirtualScrollbar` component.
|
|
321
|
+
- `axis`: `'vertical' | 'horizontal'`
|
|
322
|
+
- `totalSize`: virtual content size in pixels.
|
|
323
|
+
- `position`: current virtual scroll offset.
|
|
324
|
+
- `viewportSize`: virtual visible area size.
|
|
325
|
+
- `scrollToOffset`: `(offset: number) => void`
|
|
326
|
+
- `containerId`: unique ID of the container.
|
|
327
|
+
- `isRtl`: `boolean`
|
|
328
|
+
- `isDragging`: whether the scrollbar thumb is currently being dragged.
|
|
329
|
+
|
|
330
|
+
#### `ScrollDetails`
|
|
331
|
+
- `items`: `RenderedItem<T>[]`
|
|
332
|
+
- `currentIndex`: number (first visible row index below header)
|
|
333
|
+
- `currentColIndex`: number (first visible column index after sticky)
|
|
334
|
+
- `currentEndIndex`: number
|
|
335
|
+
- `currentEndColIndex`: number
|
|
336
|
+
- `scrollOffset`: `{ x, y }` (VU)
|
|
337
|
+
- `displayScrollOffset`: `{ x, y }` (DU)
|
|
338
|
+
- `viewportSize`: `{ width, height }` (VU)
|
|
339
|
+
- `displayViewportSize`: `{ width, height }` (DU)
|
|
340
|
+
- `totalSize`: `{ width, height }` (VU)
|
|
341
|
+
- `isScrolling`: boolean
|
|
342
|
+
- `isProgrammaticScroll`: boolean
|
|
343
|
+
- `range`: `{ start, end }`
|
|
344
|
+
- `columnRange`: `ColumnRange`
|
|
345
|
+
|
|
346
|
+
#### `ColumnRange`
|
|
347
|
+
- `start`: number
|
|
348
|
+
- `end`: number
|
|
349
|
+
- `padStart`: number (VU)
|
|
350
|
+
- `padEnd`: number (VU)
|
|
351
|
+
|
|
352
|
+
#### `RenderedItem`
|
|
353
|
+
- `item`: `T`
|
|
354
|
+
- `index`: number
|
|
355
|
+
- `offset`: `{ x, y }` (DU)
|
|
356
|
+
- `size`: `{ width, height }` (VU)
|
|
357
|
+
- `originalX` / `originalY`: number (VU)
|
|
358
|
+
- `isSticky`: boolean
|
|
359
|
+
- `isStickyActive`: boolean
|
|
360
|
+
- `stickyOffset`: `{ x, y }` (DU)
|
|
361
|
+
|
|
362
|
+
### Methods
|
|
363
|
+
|
|
364
|
+
The following methods are exposed by the `VirtualScroll` component and the `useVirtualScroll` composable:
|
|
365
|
+
|
|
366
|
+
- `scrollToIndex(rowIndex, colIndex, options)`: Ensures a specific item is visible.
|
|
367
|
+
- `scrollToOffset(x, y, options)`: Scrolls to an absolute pixel position.
|
|
368
|
+
- `refresh()`: Resets all dynamic measurements and state.
|
|
369
|
+
- `getRowHeight(index)` / `getColumnWidth(index)`: Returns calculated sizes.
|
|
370
|
+
- `updateItemSize` / `updateItemSizes`: Manually registers new measurements.
|
|
371
|
+
- `updateHostOffset()`: Recalculates the component's relative position.
|
|
372
|
+
- `updateDirection()`: Manually triggers RTL/LTR detection.
|
|
373
|
+
- `stopProgrammaticScroll()`: Halts any active smooth scroll animation.
|
|
420
374
|
|
|
421
|
-
|
|
422
|
-
1. **Server-Side**: Only the specified range of items is rendered. Items are rendered in-flow (relative positioning) with their offsets adjusted so the specified range appears at the top-left of the container.
|
|
423
|
-
2. **Client-Side Hydration**:
|
|
424
|
-
- The component initially renders the SSR content to match the server-generated HTML.
|
|
425
|
-
- On mount, it expands the container size and automatically scrolls to exactly match the pre-rendered range using `align: 'start'`.
|
|
426
|
-
- It then seamlessly transitions to virtual mode (absolute positioning) while maintaining the scroll position.
|
|
375
|
+
For detailed type definitions and utility functions, see the [Full API Reference](https://pdandev.github.io/virtual-scroll/docs).
|
|
427
376
|
|
|
428
377
|
## License
|
|
429
378
|
|