Site Update in progress. Please excuse any potential mess. 😊
Menu

VirtualList

From Singtaa Discord

Working rendering 10000 items in a virtualized list purely in JS. Still ~200 FPS while scrolling on my 2015 Macbook Pro

original code taken from https://github.com/developit/preact-virtual-list/blob/master/src/index.js (edited)

  • Definition
import { Dom } from "OneJS/Dom";
import { Component, h } from "preact"
import { useState, useEffect, useRef, useReducer } from "preact/hooks";
import { Style } from "preact/jsx";
import { ScrollView } from "UnityEngine/UIElements";

const STYLE_INNER: Style = {
    position: "Relative", overflow: "Hidden", width: "100%", minHeight: "100%"
}

const STYLE_CONTENT: Style = {
    position: "Absolute", left: 0, overflow: "Visible", width: "100%", height: "100%"
}

interface Props {
    data: any[],
    renderRow: Function,
    rowHeight: number,
    overscanCount: number,
    style?: Style
}

export const VirtualList = (props: Props) => {
    const forceUpdate = useReducer(() => ({}), {})[1] as () => void
    const scrollviewRef = useRef<Dom>()
    const [height, setHeight] = useState(0)
    const [start, setStart] = useState(0)

    const resize = () => {
        let sv = scrollviewRef.current.ve as ScrollView
        let offetHeight = sv.resolvedStyle.height
        if (height !== offetHeight) {
            setHeight(offetHeight)
        }
    }

    const handleScroll = (scrollTop: number) => {
        let s = (scrollTop / props.rowHeight) | 0
        s = Math.max(0, s - (s % props.overscanCount))
        setStart(s);
    }

    useEffect(() => {
        resize()
        let sv = scrollviewRef.current.ve as ScrollView
        sv.verticalScroller.add_valueChanged(handleScroll)
    }, [])

    let visibleRowCount = (height / props.rowHeight) | 0
    if (props.overscanCount) {
        visibleRowCount += props.overscanCount
    }

    let end = start + 1 + visibleRowCount

    const selection = [] as any
    for (let i = start; i < end; i++) {
        selection[i - start] = props.data[i]
    }

    return <scrollview ref={scrollviewRef} style={props.style}>
        <div style={{ ...STYLE_INNER, height: props.data.length * props.rowHeight }}>
            <div style={{ ...STYLE_CONTENT, top: start * props.rowHeight }}>
                {selection.map(props.renderRow)}
            </div>
        </div>
    </scrollview>
}
  • Usage
import { h, render } from "preact"
import { VirtualList } from "VirtualList"

const DATA = [];
for (let x = 1e4; x--;) DATA[x] = `Item #${x + 1}`;

render(<VirtualList
    data={DATA}
    renderRow={row => <div style={{height: 22}}>{row}</div>}
    rowHeight={22}
    overscanCount={10}
    style={{ height: "100%" }}
/>, document.body)