-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Replace custom virtual scroll with library for better resize performance
- Loading branch information
1 parent
48b3058
commit f1fbd4d
Showing
4 changed files
with
55 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,100 +1,54 @@ | ||
<script generics="T" lang="ts"> | ||
import { createVirtualizer } from '@tanstack/svelte-virtual'; | ||
import _ from 'lodash'; | ||
import { tick } from 'svelte'; | ||
// eslint-disable-next-line no-undef | ||
export let items: T[]; | ||
export let itemHeight: number | undefined = undefined; | ||
export let bench = 10; | ||
export let itemHeight: number; | ||
export let overscan = 5; | ||
let clazz = ''; | ||
export { clazz as class }; | ||
export let containerClass = ''; | ||
let start = 0; | ||
let end = 10; | ||
let viewport: HTMLElement; | ||
let container: HTMLElement; | ||
let heightMap: number[] = []; | ||
export let itemClass = ''; | ||
$: { | ||
heightMap = Array.from({ length: items.length }); | ||
if(viewport && container && items.length > 0) { | ||
tick().then(updateVisible); | ||
} | ||
} | ||
function updateHeightMap() { | ||
const virtualRows = Array.from(container?.children ?? []); | ||
virtualRows.forEach((elem, idx) => { | ||
heightMap[start + idx] = elem.clientHeight; | ||
}); | ||
} | ||
let virtualListEl: HTMLDivElement; | ||
$: knownHeights = heightMap.filter((x) => !!x); | ||
$: averageHeight = knownHeights.reduce((acc, curr) => acc + curr, 0) / knownHeights.length; | ||
$: virtualizer = createVirtualizer<HTMLDivElement, HTMLDivElement>({ | ||
count: items.length, | ||
getScrollElement: () => virtualListEl, | ||
estimateSize: () => itemHeight ?? 100, | ||
overscan, | ||
}); | ||
function getHeight(item: number): number { | ||
return heightMap[item] ?? (itemHeight ?? averageHeight); | ||
} | ||
let viewportHeight: number; | ||
$: if(viewport) { | ||
// Add or remove elements when the viewport height changes | ||
viewportHeight; | ||
items; | ||
updateVisible(); | ||
} | ||
async function updateVisible() { | ||
const { scrollTop } = viewport; | ||
updateHeightMap(); | ||
let height = 0; | ||
let newStart = 0; | ||
while(newStart < items.length && height + getHeight(newStart) < scrollTop) { | ||
height += getHeight(newStart); | ||
newStart++; | ||
} | ||
let newEnd = newStart; | ||
while(newEnd < items.length && height < scrollTop + viewport.clientHeight) { | ||
height += getHeight(newEnd); | ||
newEnd++; | ||
} | ||
start = Math.max(newStart - bench, 0); | ||
end = Math.min(newEnd + bench, items.length); | ||
} | ||
$: top = _.range(0, start).map(getHeight).reduce((acc, curr) => acc + curr, 0); | ||
$: bottom = _.range(end, items.length).map(getHeight).reduce((acc, curr) => acc + curr, 0); | ||
$: visibleItems = items.map((data, index) => ({ index, data })).slice(start, end); | ||
function itemCreated(_element: HTMLElement) { | ||
updateHeightMap(); | ||
$: vitems = $virtualizer.getVirtualItems(); | ||
let virtualItemEls: HTMLDivElement[] = []; | ||
$: if (virtualItemEls.length) { | ||
virtualItemEls.forEach((el) => $virtualizer.measureElement(el)); | ||
} | ||
</script> | ||
|
||
<div | ||
bind:this={viewport} | ||
bind:this={virtualListEl} | ||
style="overflow-anchor: none" | ||
class="relative overflow-y-scroll h-full {clazz}" | ||
bind:offsetHeight={viewportHeight} | ||
on:scroll={updateVisible} | ||
> | ||
<div | ||
bind:this={container} | ||
style:padding-top="{top}px" | ||
style:padding-bottom="{bottom}px" | ||
class="overflow-hidden {containerClass}" | ||
style="height: {$virtualizer.getTotalSize()}px; width: 100%;" | ||
class="overflow-hidden" | ||
> | ||
{#each visibleItems as item (item.index)} | ||
<div class="overflow-hidden" use:itemCreated> | ||
<slot item={item.data}>Missing template</slot> | ||
</div> | ||
{/each} | ||
<div | ||
style="transform: translateY({vitems[0] | ||
? vitems[0].start - $virtualizer.options.scrollMargin | ||
: 0}px);" | ||
class="absolute top-0 left-0 w-full overflow-hidden" | ||
> | ||
{#each vitems as row, idx (row.index)} | ||
<div | ||
bind:this={virtualItemEls[idx]} | ||
class="overflow-hidden {itemClass}"> | ||
<slot item={items[row.index]}>Missing template</slot> | ||
</div> | ||
{/each} | ||
</div> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters