diff --git a/VirtualList.svelte b/VirtualList.svelte index c92f65a..46bbf43 100644 --- a/VirtualList.svelte +++ b/VirtualList.svelte @@ -6,8 +6,6 @@ export let height = '100%'; export let itemHeight = undefined; - let foo; - // read-only, but visible to consumers via bind:start export let start = 0; export let end = 0; @@ -30,103 +28,114 @@ }); // whenever `items` changes, invalidate the current heightmap - $: if (mounted) refresh(items, viewport_height, itemHeight); + $: if (mounted) invalidateHeightMap(items); + + + async function invalidateHeightMap(items) { + + if (items.length === 0) { + start = 0 + end = 0 + top = 0 + bottom = 0 + height_map = [] + return; + + } + if (start > items.length - 1) { + start = items.length - 1 + top = 0 + bottom = 0 + height_map = [] + } + if (end > items.length - 1) { + end = items.length - 1 + top = 0 + bottom = 0 + height_map = [] + } + + await refresh(items, viewport_height, itemHeight); + await tick(); + await handle_scroll(); + } async function refresh(items, viewport_height, itemHeight) { + const { scrollTop } = viewport; - await tick(); // wait until the DOM is up to date + // if items has changed, we have to check to see if start and end are still in range + await tick(); // wait until the DOM is up to date let content_height = top - scrollTop; + let i = start; while (content_height < viewport_height && i < items.length) { let row = rows[i - start]; - if (!row) { end = i + 1; await tick(); // render the newly visible row row = rows[i - start]; } - const row_height = height_map[i] = itemHeight || row.offsetHeight; content_height += row_height; i += 1; } end = i; - const remaining = items.length - end; average_height = (top + content_height) / end; - bottom = remaining * average_height; height_map.length = items.length; - } async function handle_scroll() { const { scrollTop } = viewport; - const old_start = start; - for (let v = 0; v < rows.length; v += 1) { height_map[start + v] = itemHeight || rows[v].offsetHeight; } - let i = 0; let y = 0; - while (i < items.length) { const row_height = height_map[i] || average_height; if (y + row_height > scrollTop) { start = i; top = y; - break; } - y += row_height; i += 1; } - while (i < items.length) { y += height_map[i] || average_height; i += 1; - if (y > scrollTop + viewport_height) break; } - end = i; - const remaining = items.length - end; average_height = y / end; - while (i < items.length) height_map[i++] = average_height; bottom = remaining * average_height; - // prevent jumping if we scrolled up into unknown territory if (start < old_start) { await tick(); - let expected_height = 0; let actual_height = 0; - for (let i = start; i < old_start; i +=1) { if (rows[i - start]) { expected_height += height_map[i]; actual_height += itemHeight || rows[i - start].offsetHeight; } } - const d = actual_height - expected_height; viewport.scrollTo(0, scrollTop + d); } - // TODO if we overestimated the space these // rows would occupy we may need to add some // more. maybe we can just call handle_scroll again? } - // trigger initial refresh onMount(() => { rows = contents.getElementsByTagName('svelte-virtual-list-row'); @@ -141,11 +150,9 @@ -webkit-overflow-scrolling:touch; display: block; } - svelte-virtual-list-contents, svelte-virtual-list-row { display: block; } - svelte-virtual-list-row { overflow: hidden; } @@ -163,7 +170,7 @@ > {#each visible as row (row.index)} - Missing template + Missing template {/each}