@pdanpdan/virtual-scroll 0.4.0 → 0.6.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 +172 -324
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +836 -376
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1334 -741
- package/dist/index.mjs.map +1 -1
- package/dist/virtual-scroll.css +1 -1
- package/package.json +8 -2
- package/src/components/VirtualScroll.test.ts +1921 -325
- package/src/components/VirtualScroll.vue +829 -386
- 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 +869 -517
- package/src/composables/useVirtualScrollbar.test.ts +526 -0
- package/src/composables/useVirtualScrollbar.ts +244 -0
- package/src/index.ts +9 -0
- package/src/types.ts +353 -110
- package/src/utils/fenwick-tree.test.ts +39 -39
- package/src/utils/scroll.test.ts +181 -101
- package/src/utils/scroll.ts +43 -5
- package/src/utils/virtual-scroll-logic.test.ts +673 -323
- package/src/utils/virtual-scroll-logic.ts +759 -430
package/README.md
CHANGED
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
A high-performance, flexible virtual scrolling component for Vue 3.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## What is it?
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
`@pdanpdan/virtual-scroll` is a Vue 3 library designed to handle massive datasets with ease. Whether you have thousands or billions of items, it ensures smooth scrolling and minimal memory usage by only rendering what's visible on the screen.
|
|
8
|
+
|
|
9
|
+
### The Problem it Solves
|
|
10
|
+
|
|
11
|
+
1. **Performance with Large Lists:** Rendering thousands of DOM elements simultaneously can slow down the browser, lead to high memory consumption, and cause "janky" scrolling.
|
|
12
|
+
2. **Browser Scroll Limits:** Most browsers have a maximum limit for the height/width of a scrollable element (typically around 10 to 30 million pixels). If your content exceeds this, it simply stops working or becomes buggy.
|
|
13
|
+
|
|
14
|
+
### Our Solution
|
|
15
|
+
|
|
16
|
+
- **Virtualization:** We only render the items currently in the viewport (plus a small buffer), keeping the DOM light and the UI responsive.
|
|
17
|
+
- **Coordinate Scaling:** To bypass browser scroll limits, we use a dual-coordinate system. We can virtually scroll through billions of pixels by scaling internal "Virtual Units" to "Display Units" within the browser's supported range.
|
|
18
|
+
- **1:1 Movement:** Unlike many other scaled virtual scroll implementations, we ensure that 1 pixel of movement on the wheel or touch results in exactly 1 pixel of movement in the viewport, maintaining a natural feel regardless of the scale.
|
|
14
19
|
|
|
15
20
|
## Installation
|
|
16
21
|
|
|
@@ -20,13 +25,11 @@ pnpm add @pdanpdan/virtual-scroll
|
|
|
20
25
|
|
|
21
26
|
## Usage Modes
|
|
22
27
|
|
|
23
|
-
The package provides
|
|
28
|
+
The package provides several ways to integrate the component into your project.
|
|
24
29
|
|
|
25
30
|
### 1. Compiled Component (Recommended)
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
**Important:** You must manually import the CSS file for styles to work.
|
|
32
|
+
Standard way for most modern bundlers (Vite, Webpack). You must manually import the CSS file.
|
|
30
33
|
|
|
31
34
|
```vue
|
|
32
35
|
<script setup>
|
|
@@ -36,64 +39,26 @@ import '@pdanpdan/virtual-scroll/style.css';
|
|
|
36
39
|
</script>
|
|
37
40
|
```
|
|
38
41
|
|
|
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
42
|
### 2. Original Vue SFC
|
|
45
43
|
|
|
46
|
-
|
|
44
|
+
Import the raw `.vue` file if you want to use your own Vue compiler configuration.
|
|
47
45
|
|
|
48
46
|
```vue
|
|
49
47
|
<script setup>
|
|
50
48
|
import VirtualScroll from '@pdanpdan/virtual-scroll/VirtualScroll.vue';
|
|
51
|
-
// No need to import CSS
|
|
49
|
+
// No need to import CSS separately
|
|
52
50
|
</script>
|
|
53
51
|
```
|
|
54
52
|
|
|
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
53
|
### 3. CDN Usage
|
|
61
54
|
|
|
62
|
-
You can use the library directly from a CDN like unpkg or jsdelivr.
|
|
63
|
-
|
|
64
55
|
```html
|
|
65
56
|
<!-- Import Vue 3 first -->
|
|
66
57
|
<script src="https://unpkg.com/vue@3"></script>
|
|
67
|
-
|
|
68
58
|
<!-- Import VirtualScroll CSS -->
|
|
69
|
-
<link rel="stylesheet" href="https://unpkg.com/@pdanpdan/virtual-scroll/dist/
|
|
70
|
-
|
|
59
|
+
<link rel="stylesheet" href="https://unpkg.com/@pdanpdan/virtual-scroll/dist/style.css">
|
|
71
60
|
<!-- Import VirtualScroll JavaScript -->
|
|
72
61
|
<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
62
|
```
|
|
98
63
|
|
|
99
64
|
## Basic Usage
|
|
@@ -108,322 +73,205 @@ const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, label: `Item ${
|
|
|
108
73
|
</script>
|
|
109
74
|
|
|
110
75
|
<template>
|
|
111
|
-
<
|
|
112
|
-
<
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
</div>
|
|
117
|
-
</template>
|
|
118
|
-
</VirtualScroll>
|
|
119
|
-
</div>
|
|
76
|
+
<VirtualScroll :items="items" :item-size="50" class="my-container">
|
|
77
|
+
<template #item="{ item, index }">
|
|
78
|
+
<div class="my-item">{{ index }}: {{ item.label }}</div>
|
|
79
|
+
</template>
|
|
80
|
+
</VirtualScroll>
|
|
120
81
|
</template>
|
|
121
82
|
|
|
122
83
|
<style>
|
|
123
|
-
.my-container {
|
|
124
|
-
|
|
125
|
-
overflow: auto;
|
|
126
|
-
}
|
|
127
|
-
.my-item {
|
|
128
|
-
height: 50px;
|
|
129
|
-
}
|
|
84
|
+
.my-container { height: 500px; }
|
|
85
|
+
.my-item { height: 50px; }
|
|
130
86
|
</style>
|
|
131
87
|
```
|
|
132
88
|
|
|
133
|
-
##
|
|
89
|
+
## Technical Overview
|
|
134
90
|
|
|
135
|
-
|
|
91
|
+
### Scaled Virtual Scroll
|
|
136
92
|
|
|
137
|
-
|
|
93
|
+
To support massive datasets (billions of pixels) while staying within browser scroll limits, the library uses a dual-unit coordinate system:
|
|
138
94
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
| **Fixed** | `number` (e.g., `50`) | **Best** | Every item has the exact same size. Calculations are *O(1)*. |
|
|
142
|
-
| **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. No `ResizeObserver` overhead unless it differs from measured size. |
|
|
144
|
-
| **Dynamic** | `0`, `null`, or `undefined` | **Fair** | Sizes are measured automatically via `ResizeObserver` after rendering. |
|
|
95
|
+
* **VU (Virtual Units)**: The internal coordinate system representing the actual size of your content.
|
|
96
|
+
* **DU (Display Units)**: The browser's physical coordinate system (limited to `BROWSER_MAX_SIZE`).
|
|
145
97
|
|
|
146
|
-
|
|
98
|
+
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.
|
|
147
99
|
|
|
148
|
-
|
|
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.
|
|
100
|
+
### Core Rendering Rule
|
|
157
101
|
|
|
158
|
-
|
|
102
|
+
Items are rendered at their VU size and positioned using `translateY()` (or `translateX()` / `translate()`) based on the current display scroll position and their virtual offset. This prevents "jumping" and maintains sub-pixel precision even at extreme scales.
|
|
159
103
|
|
|
160
|
-
|
|
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.
|
|
104
|
+
### Performance
|
|
163
105
|
|
|
164
|
-
|
|
106
|
+
- **Fenwick Tree:** Uses a Fenwick Tree (Binary Indexed Tree) for *O(log N)* prefix sum and point updates, allowing for extremely fast calculation of item offsets even in dynamic lists with millions of items.
|
|
107
|
+
- **ResizeObserver:** Automatically handles dynamic item sizes by measuring them when they change.
|
|
108
|
+
- **Style Isolation:** Uses CSS `@layer` for style isolation and `contain: layout` for improved rendering performance.
|
|
165
109
|
|
|
166
|
-
|
|
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.
|
|
110
|
+
## Key Features
|
|
170
111
|
|
|
171
|
-
|
|
112
|
+
- **Dynamic & Fixed Sizes**: Supports uniform item sizes, variable sizes via function/array, or fully dynamic sizes via `ResizeObserver`.
|
|
113
|
+
- **Multi-Directional**: Works in `vertical`, `horizontal`, or `both` (grid) directions.
|
|
114
|
+
- **Virtual Scrollbars**: Optimized virtual scrollbars that handle massive scales and provide consistent cross-browser styling.
|
|
115
|
+
- **Container Flexibility**: Can use a custom element or the browser `window`/`body` as the scroll container.
|
|
116
|
+
- **SSR Support**: Built-in support for pre-rendering specific ranges for Server-Side Rendering.
|
|
117
|
+
- **Sticky Sections**: Support for sticky headers, footers, and indices.
|
|
118
|
+
- **Scroll Restoration**: Automatically maintains scroll position when items are prepended to the list.
|
|
119
|
+
- **RTL Support**: Full support for Right-to-Left layouts with automatic detection.
|
|
172
120
|
|
|
173
|
-
|
|
121
|
+
## Component Reference: VirtualScroll
|
|
174
122
|
|
|
175
123
|
### Props
|
|
176
124
|
|
|
177
|
-
#### Core Configuration
|
|
178
125
|
| Prop | Type | Default | Description |
|
|
179
126
|
|------|------|---------|-------------|
|
|
180
127
|
| `items` | `T[]` | Required | Array of items to be virtualized. |
|
|
181
|
-
| `itemSize` | `number
|
|
182
|
-
| `direction` | `'vertical'
|
|
183
|
-
| `gap` | `number` | `0` | Spacing between items. |
|
|
184
|
-
|
|
185
|
-
#### Grid Configuration (only for direction="both")
|
|
186
|
-
| Prop | Type | Default | Description |
|
|
187
|
-
|------|------|---------|-------------|
|
|
128
|
+
| `itemSize` | `number | fn | null` | `40` | Fixed size or function. Pass `0`/`null` for dynamic. |
|
|
129
|
+
| `direction` | `'vertical' | 'horizontal' | 'both'` | `'vertical'` | Scroll direction. |
|
|
188
130
|
| `columnCount` | `number` | `0` | Number of columns for grid mode. |
|
|
189
|
-
| `columnWidth` | `num
|
|
190
|
-
| `columnGap` | `number` | `0` | Spacing between columns. |
|
|
191
|
-
|
|
192
|
-
#### Features & Behavior
|
|
193
|
-
| Prop | Type | Default | Description |
|
|
194
|
-
|------|------|---------|-------------|
|
|
131
|
+
| `columnWidth` | `num | num[] | fn | null` | `100` | Width for columns in grid mode. |
|
|
132
|
+
| `gap` / `columnGap` | `number` | `0` | Spacing between items/columns. |
|
|
195
133
|
| `stickyIndices` | `number[]` | `[]` | Indices of items that should remain sticky. |
|
|
196
134
|
| `stickyHeader` / `stickyFooter` | `boolean` | `false` | If true, measures and adds slot size to padding. |
|
|
197
|
-
| `ssrRange` | `object` |
|
|
198
|
-
| `
|
|
199
|
-
| `loadDistance` | `number` | `200` | Distance from end to trigger `load` event. |
|
|
135
|
+
| `ssrRange` | `object` | - | Range of items to pre-render for SSR. |
|
|
136
|
+
| `virtualScrollbar` | `boolean` | `false` | Whether to force virtual scrollbars. |
|
|
200
137
|
| `restoreScrollOnPrepend` | `boolean` | `false` | Maintain position when items added to top. |
|
|
201
|
-
| `
|
|
202
|
-
| `
|
|
203
|
-
|
|
204
|
-
#### Advanced & Performance
|
|
205
|
-
| Prop | Type | Default | Description |
|
|
206
|
-
|------|------|---------|-------------|
|
|
207
|
-
| `container` | `HTMLElement \| Window` | `host element` | The scrollable container element. |
|
|
208
|
-
| `scrollPaddingStart` / `End` | `num \| {x, y}` | `0` | Padding for scroll calculations. |
|
|
209
|
-
| `containerTag` / `wrapperTag` / `itemTag` | `string` | `'div'` | HTML tags for component parts. |
|
|
138
|
+
| `container` | `HTMLElement | Window` | `hostRef` | The scrollable container element. |
|
|
139
|
+
| `scrollPaddingStart` / `End` | `num | {x, y}` | `0` | Padding for scroll calculations. |
|
|
210
140
|
| `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
|
-
- `load`: Triggered near the end of content. Payload: `'vertical' | 'horizontal'`.
|
|
232
|
-
- `visibleRangeChange`: Emitted when rendered indices change. Payload: `{ start, end, colStart, colEnd }`.
|
|
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.
|
|
141
|
+
| `initialScrollIndex` | `number` | `undefined` | Index to jump to on mount. |
|
|
142
|
+
| `initialScrollAlign` | `'start' | 'center' | 'end' | 'auto'` | `'start'` | Alignment for initial jump. |
|
|
143
|
+
| `defaultItemSize` / `defaultColumnWidth` | `number` | `40 / 100` | Estimate for dynamic items/columns. |
|
|
144
|
+
| `debug` | `boolean` | `false` | Enable debug visualization. |
|
|
145
|
+
|
|
146
|
+
### Slots
|
|
147
|
+
|
|
148
|
+
- `item`: Scoped slot for individual items. Provides `item`, `index`, `columnRange`, `getColumnWidth`, `gap`, `columnGap`, `isSticky`, `isStickyActive`, `isStickyActiveX`, `isStickyActiveY`, `offset`.
|
|
149
|
+
- `header` / `footer`: Content rendered at the top/bottom of the scrollable area.
|
|
150
|
+
- `loading`: Content shown at the end when `loading` prop is true.
|
|
151
|
+
- `scrollbar`: Scoped slot for custom scrollbar. Called once for each active axis.
|
|
152
|
+
- `axis`: `'vertical' | 'horizontal'`
|
|
153
|
+
- `positionPercent`: current position (0-1).
|
|
154
|
+
- `viewportPercent`: viewport percentage (0-1).
|
|
155
|
+
- `thumbSizePercent`: Calculated thumb size (0-100).
|
|
156
|
+
- `thumbPositionPercent`: Calculated thumb position (0-100).
|
|
157
|
+
- `trackProps`: Attributes/listeners for the track. Bind with `v-bind="trackProps"`.
|
|
158
|
+
- `thumbProps`: Attributes/listeners for the thumb. Bind with `v-bind="thumbProps"`.
|
|
159
|
+
- `scrollbarProps`: Grouped props for the `VirtualScrollbar` component.
|
|
160
|
+
- `isDragging`: Whether the thumb is currently being dragged.
|
|
256
161
|
|
|
257
162
|
### Exposed Members
|
|
258
163
|
|
|
259
|
-
|
|
164
|
+
The following properties and methods are available on the `VirtualScroll` component instance (via template `ref`).
|
|
165
|
+
|
|
166
|
+
#### Properties
|
|
167
|
+
- **All Props**: All properties defined in [Props](#props) are available on the instance.
|
|
168
|
+
- `scrollDetails`: Full reactive state of the virtual scroll system.
|
|
169
|
+
- `columnRange`: Information about the current visible range of columns.
|
|
170
|
+
- `isHydrated`: `true` when the component is mounted and hydrated.
|
|
171
|
+
- `isRtl`: `true` if the container is in Right-to-Left mode.
|
|
172
|
+
- `scrollbarPropsVertical` / `scrollbarPropsHorizontal`: Reactive `ScrollbarSlotProps`.
|
|
173
|
+
- `scaleX` / `scaleY`: Current coordinate scaling factors (VU/DU).
|
|
174
|
+
- `renderedWidth` / `renderedHeight`: Physical dimensions in DOM (clamped, DU).
|
|
175
|
+
- `componentOffset`: Absolute offset of the component within its container (DU).
|
|
176
|
+
|
|
177
|
+
#### Methods
|
|
178
|
+
- `scrollToIndex(row, col, options)`: Programmatic scroll to index.
|
|
179
|
+
- `scrollToOffset(x, y, options)`: Programmatic scroll to pixel position.
|
|
180
|
+
- `refresh()`: Resets all measurements and state.
|
|
181
|
+
- `stopProgrammaticScroll()`: Halt smooth scroll animations.
|
|
182
|
+
- `updateDirection()`: Manually trigger direction detection.
|
|
183
|
+
- `updateHostOffset()`: Recalculate component position.
|
|
184
|
+
- `updateItemSize(index, w, h, el?)`: Register single measurement.
|
|
185
|
+
- `updateItemSizes(updates)`: Batch register measurements.
|
|
186
|
+
- `getRowHeight(index)`: Returns the calculated height of a row.
|
|
187
|
+
- `getColumnWidth(index)`: Returns the calculated width of a column.
|
|
188
|
+
- `getRowOffset(index)`: Returns the virtual offset of a row.
|
|
189
|
+
- `getColumnOffset(index)`: Returns the virtual offset of a column.
|
|
190
|
+
- `getItemOffset(index)`: Returns the virtual offset of an item.
|
|
191
|
+
- `getItemSize(index)`: Returns the size of an item along the scroll axis.
|
|
192
|
+
|
|
193
|
+
## Sizing Guide
|
|
194
|
+
|
|
195
|
+
| Option Type | `itemSize` / `columnWidth` | Performance | Description |
|
|
196
|
+
|-------------|----------------------------|-------------|-------------|
|
|
197
|
+
| **Fixed** | `number` (e.g., `50`) | **Best** | Every item has the exact same size. Calculations are *O(1)*. |
|
|
198
|
+
| **Array** | `number[]` (cols only) | **Great** | Each column has a fixed size from the array (cycles if shorter). |
|
|
199
|
+
| **Function** | `(item, index) => number` | **Good** | Size is known but varies per item. |
|
|
200
|
+
| **Dynamic** | `0`, `null`, or `undefined` | **Fair** | Sizes are measured automatically via `ResizeObserver`. |
|
|
260
201
|
|
|
261
|
-
|
|
262
|
-
- `columnRange`: Information about the current visible range of columns.
|
|
263
|
-
- `getColumnWidth(index: number)`: Helper to get the calculated width of a specific column.
|
|
264
|
-
- `scrollToIndex(rowIndex, colIndex, options)`: Programmatic scroll to a specific row and/or column (default `align: 'auto'`, `behavior: 'auto'`).
|
|
265
|
-
- `scrollToOffset(x, y, options)`: Programmatic scroll to a specific pixel position (default `behavior: 'auto'`).
|
|
266
|
-
- `refresh()`: Resets all dynamic measurements and re-initializes state.
|
|
267
|
-
- `stopProgrammaticScroll()`: Immediately halts any active smooth scroll animation.
|
|
202
|
+
## Virtual Scrollbars
|
|
268
203
|
|
|
269
|
-
|
|
204
|
+
Virtual scrollbars are automatically enabled when content size exceeds browser limits, but can be forced via the `virtualScrollbar` prop.
|
|
270
205
|
|
|
271
|
-
|
|
206
|
+
**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.
|
|
272
207
|
|
|
273
|
-
###
|
|
208
|
+
### Using the `VirtualScrollbar` Component
|
|
274
209
|
|
|
275
|
-
|
|
276
|
-
/* eslint-disable unused-imports/no-unused-vars */
|
|
277
|
-
import { useVirtualScroll } from '@pdanpdan/virtual-scroll';
|
|
278
|
-
import { computed, ref } from 'vue';
|
|
210
|
+
You can use the built-in `VirtualScrollbar` independently if needed.
|
|
279
211
|
|
|
280
|
-
|
|
281
|
-
|
|
212
|
+
```vue
|
|
213
|
+
<script setup>
|
|
214
|
+
import { VirtualScrollbar } from '@pdanpdan/virtual-scroll';
|
|
215
|
+
import { ref } from 'vue';
|
|
282
216
|
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
container: containerRef.value,
|
|
287
|
-
direction: 'vertical' as const,
|
|
288
|
-
}));
|
|
217
|
+
const scrollX = ref(0);
|
|
218
|
+
const scrollY = ref(0);
|
|
219
|
+
</script>
|
|
289
220
|
|
|
290
|
-
|
|
221
|
+
<template>
|
|
222
|
+
<div class="my-container relative overflow-hidden">
|
|
223
|
+
<VirtualScrollbar
|
|
224
|
+
axis="vertical"
|
|
225
|
+
:total-size="10000"
|
|
226
|
+
:viewport-size="500"
|
|
227
|
+
:position="scrollY"
|
|
228
|
+
@scroll-to-offset="val => scrollY = val"
|
|
229
|
+
/>
|
|
230
|
+
<VirtualScrollbar
|
|
231
|
+
axis="horizontal"
|
|
232
|
+
:total-size="10000"
|
|
233
|
+
:viewport-size="800"
|
|
234
|
+
:position="scrollX"
|
|
235
|
+
@scroll-to-offset="val => scrollX = val"
|
|
236
|
+
/>
|
|
237
|
+
</div>
|
|
238
|
+
</template>
|
|
291
239
|
```
|
|
292
240
|
|
|
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. |
|
|
312
|
-
|
|
313
|
-
## API Reference
|
|
314
|
-
|
|
315
|
-
### Types
|
|
316
|
-
|
|
317
|
-
#### ScrollDetails<T>
|
|
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'`
|
|
365
|
-
|
|
366
|
-
- `start`: Align to top/left edge.
|
|
367
|
-
- `center`: Align to viewport center.
|
|
368
|
-
- `end`: Align to bottom/right edge.
|
|
369
|
-
- `auto`: **Smart Alignment**. Only scrolls if item is not fully visible.
|
|
370
|
-
|
|
371
|
-
### Methods
|
|
372
|
-
|
|
373
|
-
#### `scrollToIndex(rowIndex, colIndex, options)`
|
|
374
|
-
Ensures a specific item is visible within the viewport. Corrects position if item sizes are dynamic.
|
|
375
|
-
|
|
376
|
-
#### `scrollToOffset(x, y, options)`
|
|
377
|
-
Scrolls the container to an absolute pixel position.
|
|
378
|
-
|
|
379
|
-
#### `stopProgrammaticScroll()`
|
|
380
|
-
Immediately halts any active smooth scroll animation.
|
|
381
|
-
|
|
382
|
-
#### `updateItemSize(index, width, height, element?)`
|
|
383
|
-
Manually registers a new measurement for a single item.
|
|
384
|
-
|
|
385
|
-
#### `updateItemSizes(updates)`
|
|
386
|
-
Batched version of `updateItemSize`.
|
|
387
|
-
|
|
388
|
-
#### `updateHostOffset()`
|
|
389
|
-
Forces a recalculation of the host element's position relative to the scroll container.
|
|
390
|
-
|
|
391
|
-
#### `getColumnWidth(index)`
|
|
392
|
-
Returns the currently calculated width for a specific column index.
|
|
393
|
-
|
|
394
|
-
#### `refresh()`
|
|
395
|
-
Invalidates all cached measurements and triggers a full re-initialization.
|
|
396
|
-
|
|
397
|
-
## Utility Functions
|
|
398
|
-
|
|
399
|
-
The library exports several utility functions and classes:
|
|
400
|
-
|
|
401
|
-
- `isElement(val: any): val is HTMLElement`: Checks if value is an `HTMLElement` (excludes `window`).
|
|
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.
|
|
407
|
-
|
|
408
|
-
## SSR Support
|
|
409
|
-
|
|
410
|
-
The component is designed to be SSR-friendly. You can pre-render a specific range of items on the server using the `ssrRange` prop.
|
|
241
|
+
### Using the `scrollbar` Slot
|
|
242
|
+
|
|
243
|
+
The `scrollbar` slot provides everything needed to build a fully custom interface using `v-bind`. It is called once for each active axis.
|
|
411
244
|
|
|
412
245
|
```vue
|
|
413
|
-
<
|
|
414
|
-
:items="items"
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
246
|
+
<template>
|
|
247
|
+
<VirtualScroll :items="items" direction="both" virtual-scrollbar>
|
|
248
|
+
<template #scrollbar="{ trackProps, thumbProps, axis }">
|
|
249
|
+
<!-- Handle axes separately -->
|
|
250
|
+
<div v-if="axis === 'vertical'" v-bind="trackProps" class="custom-v-track">
|
|
251
|
+
<div v-bind="thumbProps" class="custom-v-thumb" />
|
|
252
|
+
</div>
|
|
253
|
+
<div v-else v-bind="trackProps" class="custom-h-track">
|
|
254
|
+
<div v-bind="thumbProps" class="custom-h-thumb" />
|
|
255
|
+
</div>
|
|
256
|
+
</template>
|
|
257
|
+
</VirtualScroll>
|
|
258
|
+
</template>
|
|
419
259
|
```
|
|
420
260
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
261
|
+
### CSS Variables for Default Scrollbar
|
|
262
|
+
|
|
263
|
+
| Variable | Default (Light/Dark) | Description |
|
|
264
|
+
|----------|-----------------|-------------|
|
|
265
|
+
| `--vs-scrollbar-bg` | `rgba(230,230,230,0.9) / rgba(30,30,30,0.9)` | Track background color. |
|
|
266
|
+
| `--vs-scrollbar-thumb-bg` | `rgba(0,0,0,0.3) / rgba(255,255,255,0.3)` | Thumb background color. |
|
|
267
|
+
| `--vs-scrollbar-thumb-hover-bg` | `rgba(0,0,0,0.6) / rgba(255,255,255,0.6)` | Thumb background on hover/active. |
|
|
268
|
+
| `--vs-scrollbar-size` | `8px` | Width/height of the scrollbar. |
|
|
269
|
+
| `--vs-scrollbar-radius` | `4px` | Border radius for track and thumb. |
|
|
270
|
+
|
|
271
|
+
## Composables
|
|
272
|
+
|
|
273
|
+
- `useVirtualScroll(props)`: Core logic for virtualization.
|
|
274
|
+
- `useVirtualScrollbar(props)`: Logic for scrollbar interactions.
|
|
427
275
|
|
|
428
276
|
## License
|
|
429
277
|
|