sveltejs / svelte-virtual-list

A virtual list component for Svelte apps
https://svelte.dev/repl/f78ddd84a1a540a9a40512df39ef751b
Other
682 stars 57 forks source link

It doesn't seem to work with Semantic HTML #66

Open jithujoshyjy opened 1 year ago

jithujoshyjy commented 1 year ago

I was wondering if there's a way to make VirtualList support tags like <ul> to be the list a container having <li>s as list items or how can a <table> be rendered using it?

jithujoshyjy commented 1 year ago

Here's a minimal yet incomplete solution that I have come up with:

<script lang="ts">
    import { onMount } from "svelte";

    export let data: unknown[] = [],
        containerTag = "div",
        containerHeight: string,
        itemHeight: string,
        renderAhead: number = 5,
        itemTag = "div",
        scrollbarWidth = "unset";

    const itemCount = data.length;

    let scrollTop = 0,
        rowHeight = 0,
        totalContentHeight = 0,
        startNodeIdx = 0,
        viewportHeight = 0,
        visibleNodeCount = 1,
        offsetY = 0;

    let slice = data.slice(startNodeIdx, startNodeIdx + visibleNodeCount);

    let virtualBoxNode: HTMLElement;
    const handleScroll = (evt: UIEvent) =>
        requestAnimationFrame(updateOnScroll);

    onMount(() => {
        const firstChild = virtualBoxNode.firstElementChild;
        if (!firstChild) return;

        viewportHeight = parseFloat(getComputedStyle(virtualBoxNode).height);
        rowHeight = parseFloat(getComputedStyle(firstChild).borderRadius);
        totalContentHeight = rowHeight * itemCount;
        updateOnScroll();
    });

    function updateOnScroll() {
        scrollTop = virtualBoxNode.scrollTop;

        startNodeIdx =
            Math.max(0, Math.floor(scrollTop / rowHeight) - renderAhead) || 0;

        visibleNodeCount = Math.min(
            itemCount - startNodeIdx,
            Math.ceil(viewportHeight / rowHeight) + 2 * renderAhead
        );

        offsetY = startNodeIdx * rowHeight;
        slice = data.slice(startNodeIdx, startNodeIdx + visibleNodeCount);
    }
</script>

<div
    class="virtual-scroll-box"
    bind:this={virtualBoxNode}
    on:scroll={handleScroll}
    style:--height={containerHeight}
    style:--scrollbar-width={scrollbarWidth}
>
    <span style:border-radius={itemHeight} />
    <div class="viewport" style:--viewport-height={totalContentHeight + "px"}>
        <svelte:element
            this={containerTag}
            class="virtual-scroll-container"
            style:--translate-y={offsetY + "px"}
        >
            {#each slice as item, i}
                <svelte:element
                    this={itemTag}
                    class="virtual-scroll-item"
                    style:--item-height={rowHeight + "px"}
                >
                    <slot {item} index={i} />
                </svelte:element>
            {/each}
        </svelte:element>
    </div>
</div>

<style lang="postcss">
    .virtual-scroll-box {
        --height: 100%;
        --scrollbar-width: unset;
        @apply overflow-y-auto w-full h-[var(--height)];
    }
    .virtual-scroll-box::-webkit-scrollbar {
        @apply w-[var(--scrollbar-width)];
    }
    .viewport {
        --viewport-height: 100%;
        @apply overflow-hidden will-change-transform h-[var(--viewport-height)] relative;
    }
    .virtual-scroll-container {
        --translate-y: 0px;
        @apply translate-y-[var(--translate-y)] will-change-transform;
    }
    .virtual-scroll-item {
        --item-height: auto;
        @apply h-[var(--item-height)];
    }
</style>