Load more items as the user scrolls - a common pattern for feeds and lists.
Basic Implementation
svelte<script> let items = $state([]); let isLoading = $state(false); let hasMore = $state(true); let page = $state(0); async function loadMore() { if (isLoading || !hasMore) return; isLoading = true; try { const newItems = await fetchItems(page); items = [...items, ...newItems]; page++; hasMore = newItems.length > 0; } finally { isLoading = false; } } // Intersection Observer for trigger element function observeElement(node) { const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting) { loadMore(); } }, { rootMargin: '200px' } // Load before reaching bottom ); observer.observe(node); return { destroy: () => observer.disconnect() }; } // Initial load loadMore(); </script> <div class="feed"> {#each items as item (item.id)} <article> <h2>{item.title}</h2> <p>{item.content}</p> </article> {/each} {#if isLoading} <div class="loading">Loading...</div> {/if} {#if hasMore && !isLoading} <div use:observeElement class="trigger"></div> {/if} {#if !hasMore} <p class="end">No more items</p> {/if} </div>
Combining with Virtualization
For the best of both worlds:
svelte<script> import VirtualList from './VirtualList.svelte'; let items = $state([]); let isLoading = $state(false); async function handleScroll(event) { const { scrollTop, scrollHeight, clientHeight } = event.currentTarget; // Near bottom - load more if (scrollHeight - scrollTop - clientHeight < 200 && !isLoading) { await loadMore(); } } </script> <VirtualList {items} onscroll={handleScroll}> <!-- Virtual list with infinite scroll --> </VirtualList>
Performance Tips
- Use placeholder heights - Prevents layout shifts
- Debounce scroll handlers - Don't fire too frequently
- Cancel pending requests - When user scrolls away
- Cache loaded data - Don't refetch on scroll back