Skip to content

Commit c7ade09

Browse files
committed
Add docs and examples
1 parent 02d019a commit c7ade09

File tree

13 files changed

+429
-93
lines changed

13 files changed

+429
-93
lines changed

README.md

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,72 @@
1-
# Svelte virtual scroll list
1+
# svelte-virtual-scroll-list
22

33
Svelte implementation of vue library [vue-virtual-scroll-list](https://github.com/tangbc/vue-virtual-scroll-list)
44

5+
Virtualized scrolling for big lists
6+
57
Online demo: [Svelte repl](https://ru.svelte.dev/repl/eae82aab17b04420885851d58de50a2e?version=3.38.2)
68

79
## Todo
810

911
- [x] virtual scroll
1012
- [x] dispatch scroll events
11-
- [ ] header footer slots
13+
- [x] header footer slots
1214
- [ ] jumps on add data to top
13-
- [ ] change position
14-
- [ ] examples
15+
- [x] change position
16+
- [x] examples
1517
- [x] npm
18+
19+
# API
20+
21+
## Props
22+
23+
|prop|type|default|description|
24+
|---|---|---|---|
25+
|data|object[]|`null`|Source for list|
26+
|key|string|`id`|Unique key for getting data from `data`|
27+
|keeps|number|`30`|Count of rendered items|
28+
|estimateSize|number|`estimateSize`|Estimate size of each item, needs for smooth scrollbar|
29+
|isHorizontal|boolean|`false`|Scroll direction|
30+
|pageMode|boolean|`false`|Let virtual list using global document to scroll through the list|
31+
|start|number|`0`|scroll position start index
32+
|offset|number|`0`|scroll position offset
33+
|topThreshold|number|`0`|The threshold to emit `top` event, attention to multiple calls.
34+
|bottomThreshold|number|`0`|The threshold to emit `bottom` event, attention to multiple calls.
35+
36+
## Methods
37+
38+
Access to methods by component binding
39+
<details>
40+
<summary>Binding example</summary>
41+
42+
```html
43+
44+
<script>
45+
let vs
46+
</script>
47+
48+
<VirtualScroll bind:this={vs}></VirtualScroll>
49+
<button on:click={vs.scrollToBottom}>To bottom</button>
50+
```
51+
52+
</details>
53+
54+
|method|arguments|description|
55+
|---|---|---|
56+
|scrollToBottom|`none`|Scroll list to bottom|
57+
|scrollToIndex|`index: number`|Set scroll position to a designated index|
58+
|scrollToOffset|`offset: number`|Set scroll position to a designated offset|
59+
|getSize|`id: typeof props.key`|Get the designated item size|
60+
|getSizes|`none`|Get the total number of stored (rendered) items|
61+
|getOffset|`none`|Get current scroll offset|
62+
|getClientSize|`none`|Get wrapper element client viewport size (width or height)|
63+
|getScrollSize|`none`|Get all scroll size (scrollHeight or scrollWidth)|
64+
|updatePageModeFront|`none`|When using page mode and virtual list root element offsetTop or offsetLeft change, you need call this method manually|
65+
66+
## Events
67+
68+
|event|description|
69+
|---|---|
70+
|scroll|Scroll event|
71+
|top|Top of the list reached|
72+
|bottom|Bottom of the list reached|

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
{
22
"name": "svelte-virtual-scroll-list",
33
"description": "Svelte lib for virtualizing lists",
4-
"version": "1.0.1",
4+
"version": "1.0.2",
55
"scripts": {
66
"build": "rollup -c rollup/rollup.config.js",
7+
"build:dev": "rollup -c rollup/rollup.config.dev.js",
78
"dev": "rollup -c rollup/rollup.config.dev.js -w",
8-
"start": "sirv public --no-clear"
9+
"start:dev": "sirv public --no-clear"
910
},
1011
"repository": {
1112
"type": "git",

public/index.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
<meta charset='utf-8'>
55
<meta name='viewport' content='width=device-width,initial-scale=1'>
66

7-
<title>Svelte app</title>
7+
<title>svelte-virtual-scroll-list</title>
88

99
<link rel='icon' type='image/png' href='/favicon.png'>
10-
<link rel='stylesheet' href='/global.css'>
11-
<link rel='stylesheet' href='/build/bundle.css'>
10+
<link href='global.css' rel='stylesheet'>
11+
<link href='build/bundle.css' rel='stylesheet'>
1212

13-
<script defer src='/build/bundle.js' type="module"></script>
13+
<script defer src='build/bundle.js' type="module"></script>
1414
</head>
1515

1616
<body>

rollup/rollup.config.dev.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ function serve() {
1515
return {
1616
writeBundle() {
1717
if (server) return
18-
server = require("child_process").spawn("npm", ["run", "start", "--", "--dev"], {
18+
server = require("child_process").spawn("npm", ["run", "start:dev", "--", "--dev"], {
1919
stdio: ["ignore", "inherit", "inherit"],
2020
shell: true,
2121
})

src/Item.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
44
export let horizontal = false
55
export let uniqueKey
6+
export let type = "item"
67
78
let resizeObserver
89
let itemDiv
@@ -32,7 +33,7 @@
3233
3334
// tell parent current size identify by unqiue key
3435
function dispatchSizeChange() {
35-
dispatch("resize", {id: uniqueKey, size: getCurrentSize()})
36+
dispatch("resize", {id: uniqueKey, size: getCurrentSize(), type})
3637
}
3738
</script>
3839

src/VirtualScroll.svelte

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33
import Item from "./Item.svelte"
44
import {createEventDispatcher, onDestroy, onMount} from "svelte"
55
6-
export let dataKey
7-
export let dataSources
6+
export let key = "id"
7+
export let data
88
export let keeps = 30
99
export let estimateSize = 50
10-
export let direction = "vertical"
10+
export let isHorizontal = false
11+
export let start = 0
1112
export let offset = 0
1213
export let pageMode = false
13-
export let topThreshold = 10
14-
export let bottomThreshold = 10
14+
export let topThreshold = 0
15+
export let bottomThreshold = 0
1516
1617
let displayItems = []
1718
let paddingStyle
18-
let isHorizontal = direction === "horizontal"
1919
let directionKey = isHorizontal ? "scrollLeft" : "scrollTop"
2020
let range = null
2121
let virtual = new Virtual({
@@ -31,6 +31,12 @@
3131
const dispatch = createEventDispatcher()
3232
3333
onMount(() => {
34+
if (start) {
35+
scrollToIndex(start)
36+
} else if (offset) {
37+
scrollToOffset(offset)
38+
}
39+
3440
if (pageMode) {
3541
updatePageModeFront()
3642
@@ -48,17 +54,26 @@
4854
})
4955
5056
function getUniqueIdFromDataSources() {
51-
return dataSources.map((dataSource) => dataSource[dataKey])
57+
return data.map((dataSource) => dataSource[key])
5258
}
5359
54-
function onItemResized({id, size}) {
55-
virtual.saveSize(id, size)
60+
function onItemResized({id, size, type}) {
61+
if (type === "item")
62+
virtual.saveSize(id, size)
63+
else {
64+
if (id === "header")
65+
virtual.updateParam("slotHeaderSize", size)
66+
else if (id === "footer")
67+
virtual.updateParam("slotFooterSize", size)
68+
69+
virtual.handleSlotSizeChange()
70+
}
5671
}
5772
5873
function onRangeChanged(range_) {
5974
range = range_
6075
paddingStyle = paddingStyle = isHorizontal ? `0px ${range.padBehind}px 0px ${range.padFront}px` : `${range.padFront}px 0px ${range.padBehind}px`
61-
displayItems = dataSources.slice(range.start, range.end + 1)
76+
displayItems = data.slice(range.start, range.end + 1)
6277
}
6378
6479
function onScroll(event) {
@@ -75,15 +90,15 @@
7590
emitEvent(offset, clientSize, scrollSize, event)
7691
}
7792
78-
function getOffset() {
93+
export function getOffset() {
7994
if (pageMode) {
8095
return document.documentElement[directionKey] || document.body[directionKey]
8196
} else {
8297
return root ? Math.ceil(root[directionKey]) : 0
8398
}
8499
}
85100
86-
function getClientSize() {
101+
export function getClientSize() {
87102
const key = isHorizontal ? "clientWidth" : "clientHeight"
88103
if (pageMode) {
89104
return document.documentElement[key] || document.body[key]
@@ -92,7 +107,7 @@
92107
}
93108
}
94109
95-
function getScrollSize() {
110+
export function getScrollSize() {
96111
const key = isHorizontal ? "scrollWidth" : "scrollHeight"
97112
if (pageMode) {
98113
return document.documentElement[key] || document.body[key]
@@ -101,7 +116,7 @@
101116
}
102117
}
103118
104-
function updatePageModeFront() {
119+
export function updatePageModeFront() {
105120
if (root) {
106121
const rect = root.getBoundingClientRect()
107122
const {defaultView} = root.ownerDocument
@@ -113,14 +128,14 @@
113128
function emitEvent(offset, clientSize, scrollSize, event) {
114129
dispatch("scroll", {event, range: virtual.getRange()})
115130
116-
if (virtual.isFront() && !!dataSources.length && (offset - topThreshold <= 0)) {
117-
dispatch("totop")
131+
if (virtual.isFront() && !!data.length && (offset - topThreshold <= 0)) {
132+
dispatch("top")
118133
} else if (virtual.isBehind() && (offset + clientSize + bottomThreshold >= scrollSize)) {
119-
dispatch("tobottom")
134+
dispatch("bottom")
120135
}
121136
}
122137
123-
function scrollToOffset(offset) {
138+
export function scrollToOffset(offset) {
124139
if (pageMode) {
125140
document.body[directionKey] = offset
126141
document.documentElement[directionKey] = offset
@@ -129,9 +144,9 @@
129144
}
130145
}
131146
132-
function scrollToIndex(index) {
147+
export function scrollToIndex(index) {
133148
// scroll to bottom
134-
if (index >= dataSources.length - 1) {
149+
if (index >= data.length - 1) {
135150
scrollToBottom()
136151
} else {
137152
const offset = virtual.getOffset(index)
@@ -158,7 +173,13 @@
158173
}
159174
}
160175
161-
$: handleDataSourcesChange(dataSources)
176+
$: scrollToOffset(offset)
177+
$: scrollToIndex(start)
178+
$: {
179+
virtual.updateParam("keeps", keeps)
180+
virtual.handleSlotSizeChange()
181+
}
182+
$: handleDataSourcesChange(data)
162183
163184
async function handleDataSourcesChange(dataSources) {
164185
// TODO: fix jump on top added data
@@ -170,13 +191,27 @@
170191
</script>
171192

172193
<div bind:this={root} on:scroll={onScroll} style="overflow-y: auto; height: inherit">
194+
{#if $$slots.header}
195+
<Item on:resize={onItemResized} type="slot" uniqueKey="header">
196+
<slot name="header"/>
197+
</Item>
198+
{/if}
173199
<div style="padding: {paddingStyle}">
174200
{#each displayItems as data}
175-
<Item on:resize={(e) => onItemResized(e.detail)} uniqueKey={data[dataKey]} horizontal={isHorizontal}>
201+
<Item
202+
on:resize={(e) => onItemResized(e.detail)}
203+
uniqueKey={data[key]}
204+
horizontal={isHorizontal}
205+
type="item">
176206
<slot {data}/>
177207
</Item>
178208
{/each}
179209
</div>
210+
{#if $$slots.footer}
211+
<Item on:resize={onItemResized} type="slot" uniqueKey="footer">
212+
<slot name="footer"/>
213+
</Item>
214+
{/if}
180215
<div bind:this={shepherd} class="shepherd"
181216
style="width: {isHorizontal ? '0px' : '100%'};height: {isHorizontal ? '100%' : '0px'}"></div>
182217
</div>

0 commit comments

Comments
 (0)