npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-virtual-overflow

v1.1.2

Published

virtual scroll without headache

Downloads

55

Readme

NPM Version GitHub stars

react-virtual-overflow

Similar to react-virtualized, but:

  • No deps
  • No fighting with containers
  • No magical divs will wrap your list with position: absolute and height: 0
  • No scroll syncing problems
  • No AutoWindow over AutoSize with VerticalSpecialList
  • Dead simple infinity loader
  • Full rendering controll
  • It just works
  • ~0.5kb gzipped

Currently only fixed item sizes supported, but will add dynamic sizing later.

Components & hooks in this library will automatically find all containers with overflows and render only visible items.
So you could stack and wrap your list in anyway you want, everything will work.

You also could use some parts of this library for example to calculate only visible on screen rect of element.

This library track only scroll/resize/orientationchange events (on capture phase), so if you resize some wrapper with overflow through styles, you should use hooks and call recalculation manually.

npm i react-virtual-overflow

demo app code
working demo

Simple example

import { VirtualListY } from "react-virtual-overflow/lib/fixed-list-y";

function MyApp() {
    const items = Array.from({ length: 300 }).map((_, i) => `item ${i}`);

    const itemHeight = 40;

    const renderItem = (item) => (
        <div style={{ height: '40px' }}>{item}</div>
    );

    return (
        <div style={{ overflowY: 'scroll', height: '300px', background: 'lightgreen' }}>
            {/* !this component will not add container with overflow! */}
            {/* the only overflow here is element above */}
            <VirtualListY
                items={items}
                itemHeight={itemHeight}
                itemKey={x => x}
                renderItem={renderItem}
            />
        </div>
    );
}

Advanced example

Advanced example with hook

import { useVirtualOverflowY } from "react-virtual-overflow";

function MyApp() {
    const items = Array.from({ length: 300 }).map((_, i) => `item ${i}`);

    const containerRef = useRef<HTMLDivElement>(undefined!);

    const itemHeight = 40;

    const { renderedItems } = useVirtualOverflowY({
        containerRef,
        itemsLengthY: items.length,
        itemHeight,
        renderItem: (itemIndex, offsetTop) => {
            const item = items[itemIndex];
            
            return (
                <div style={{ position: "absolute", top: `${offsetTop}px` }} key={item}>
                    {item}
                </div>
            )
        },
    }, []);

    return (
        <div style={{ overflowY: "scroll", height: "300px" }}>
            <div ref={containerRef} style={{ height: `${itemHeight * items.length}px` }}>
                {renderedItems}
            </div>
        </div>
    );
}

Available hooks & components

This component is used to render vertical list

import { VirtualListY } from "react-virtual-overflow/lib/fixed-list-y";

type VirtualListYProps<ItemT> = {
    items: ItemT[],
    itemHeight: number,
    // used to calculate react key when rendering
    itemKey: (item: ItemT, itemIndex: number) => string,
    overscanItemsCount?: number,
    renderItem: (item: ItemT, itemIndex: number, contentTopOffset: number) => React.ReactNode,
    calcVisibleRect?: VirtualOverflowCalcVisibleRectFn
};

function MyApp() {
    const items = Array.from({ length: 300 }).map((_, i) => `item ${i}`);

    const itemHeight = 40;

    const renderItem = (item) => (
        <div style={{ height: '40px' }}>{item}</div>
    );

    return (
        <div style={{ overflowY: 'scroll', height: '300px', background: 'lightgreen' }}>
            <VirtualListY
                items={items}
                itemHeight={itemHeight}
                itemKey={x => x}
                renderItem={renderItem}
            />
        </div>
    );
}

This component is used to render horizontal list

import { VirtualListX } from "react-virtual-overflow/lib/fixed-list-x";

type VirtualListXProps<ItemT> = {
    items: ItemT[],
    itemWidth: number,
    itemKey: (item: ItemT, itemIndex: number) => string,
    overscanItemsCount?: number,
    renderItem: (item: ItemT, itemIndex: number, contentTopOffset: number) => React.ReactNode,
    calcVisibleRect?: VirtualOverflowCalcVisibleRectFn
};

function MyApp() {
    const items = Array.from({ length: 300 }).map((_, i) => `item ${i}`);

    const itemWidth = 40;

    const renderItem = (item) => (
        <div style={{ width: '40px' }}>{item}</div>
    );

    return (
        <div style={{ overflowX: 'scroll', height: '300px', background: 'lightgreen' }}>
            <VirtualListX
                items={items}
                itemWidth={itemWidth}
                itemKey={x => x}
                renderItem={renderItem}
            />
        </div>
    );
}

This component is used to render grid

import { VirtualGrid } from "react-virtual-overflow/lib/fixed-grid";

type VirtualGridProps<ItemT> = {
    // rows
    items: ItemT[][],
    columnsNum: number,
    itemWidth: number,
    itemHeight: number,
    itemKey: (item: ItemT, itemIndexX: number, itemIndexY: number) => string,
    overscanItemsCount?: number,
    renderItem: (item: ItemT, itemIndexX: number, leftOffsetPx: number, itemIndexY: number, topOffsetPx: number) => React.ReactNode,
    calcVisibleRect?: VirtualOverflowCalcVisibleRectFn
};

function GridExample() {
    const items = itemsGrid;

    return (
        <div style={{ overflowY: 'scroll', height: '300px', background: 'lightgreen' }}>
            <VirtualGrid
                items={items}
                columnsNum={300}
                itemWidth={40}
                itemHeight={80}
                itemKey={x => x}
                overscanItemsCount={3}
                renderItem={item => <div style={{ width: '40px', height: '80px' }}>{item}</div>}
            />
        </div>
    );
}

useVirtualOverflowY hook that computes and renders vertical list

It accepts this params:

type UseVirtualOverflowParamsY = {
    // reference to container with elements (not scroll)
    containerRef: React.MutableRefObject<HTMLElement>;

    // total num of items
    itemsLengthY: number;

    // how to render each item
    renderItem: (itemIndex: number, contentTopOffsetPx: number) => React.ReactNode;

    // height of one item in pixels
    itemHeight: number;

    // how much items should be rendered beyond visible border
    // default=3
    overscanItemsCount?: number;

    // function to calculate visible rect (check utils for other options)
    calcVisibleRect?: CalcVisibleRectFn;
};

And returns:

{
    renderedItems: React.Node[],

    // method that will force update calculations
    updateViewRect: () => void,

    itemSlice: {
        topStartIndex: number;
        lengthY: number;
        leftStartIndex: number;
        lengthX: number;
    }
}

useVirtualOverflowX hook that computes and renders horizontal list

It accepts this params:

type UseVirtualOverflowParamsX = {
    // reference to container with elements (not scroll)
    containerRef: React.MutableRefObject<HTMLElement>;

    // total num of items
    itemsLengthX: number;

    // how to render each item
    renderItem: (itemIndex: number, contentLeftOffsetPx: number) => React.ReactNode;

    // width of one item in pixels
    itemWidth: number;

    // how much items should be rendered beyond visible border
    // default=3
    overscanItemsCount?: number;

    // function to calculate visible rect (check utils for other options)
    calcVisibleRect?: CalcVisibleRectFn;
};

And returns:

{
    renderedItems: React.Node[],

    // method that will force update calculations
    updateViewRect: () => void,

    itemSlice: {
        topStartIndex: number;
        lengthY: number;
        leftStartIndex: number;
        lengthX: number;
    }
}

useVirtualOverflowGrid hook that computes and renders grid

It accepts this params:

type UseVirtualOverflowParamsGrid = {
    // reference to container with elements (not scroll)
    containerRef: React.MutableRefObject<HTMLElement>;

    // total num of items horizontal
    itemsLengthX: number;

    // total num of items vertical
    itemsLengthY: number;

    // how to render each item
    renderItem: (itemIndexX: number, leftOffsetPx: number, itemIndexY: number, topOffsetPx: number) => React.ReactNode;

    // width of one item in pixels
    itemWidth: number;

    // height of one item in pixels
    itemHeight: number;

    // how much items should be rendered beyond visible border
    // default=3
    overscanItemsCount?: number;

    // function to calculate visible rect (check utils for other options)
    calcVisibleRect?: CalcVisibleRectFn;
};

And returns:

{
    renderedItems: React.Node[],

    // method that will force update calculations
    updateViewRect: () => void,

    itemSlice: {
        topStartIndex: number;
        lengthY: number;
        leftStartIndex: number;
        lengthX: number;
    }
}

useCalcVirtualOverflow hook that computes visible rect at calculates slice of items that should be rendered

It could be used if you want to render items manually, and you need only slice calculated

It accepts this params:

type UseVirtualOverflowParams = {
    containerRef: React.MutableRefObject<HTMLElement>,
    itemsLengthX?: number,
    itemsLengthY?: number,
    /** if undefined, then horizontal calculation will be skipped */
    itemWidth?: number,
    /** if undefined, then vertical calculation will be skipped */
    itemHeight?: number,
    /** default=3 */
    overscanItemsCount?: number,
    calcVisibleRect?: VirtualOverflowCalcVisibleRectFn,
};

And returns:

{
    itemSlice: {
        topStartIndex: number;
        lengthY: number;
        leftStartIndex: number;
        lengthX: number;
    };
    updateViewRect: () => void;
}

virtualOverflowCalcVisibleRect method will calculate on screen visible rect of some element

It accepts this params:

function virtualOverflowCalcVisibleRect(element: HTMLElement): {
    top: number;
    left: number;
    bottom: number;
    right: number;
    contentOffsetTop: number;
    contentOffsetLeft: number;
    contentVisibleHeight: number;
    contentVisibleWidth: number;
};

virtualOverflowCalcItems method will calculate slice of items from visible rect

You can pass here horizontal and vertical values from "calcVisibleRect" method.

This method is axis-agnostic, so you just first calculate vertical data by passing vertical coords of rect, and then (if you need) horizontal.

function virtualOverflowCalcItems(
    contentOffsetStartPx: number,
    contentVisibleSizePx: number,
    itemSize: number,
    overscanItemsCount: number,
    itemsLength: number
);

// returns
{
    // index of starting item that should be rendered (including overscan)
    itemStart: number,
    // total count of items (including start & end overscan)
    itemLen: number
};

// Example for vertical slice calculation:
const visibleRect = calcVisibleRect(containerRef.current);
const verticalSlice = virtualOverflowCalcItems(
    visibleRect.contentOffsetTop,
    visibleRect.contentVisibleHeight,
    itemHeight,
    overscanItemsCount,
    itemsLengthY
);

All hooks (useCalcVirtualOverflow, useVirtualOverflowY, useVirtualOverflowX, useVirtualOverflowGrid) returns itemSlice which you can use to trigger infinity loading.

For example:

const [items, setItems] = useState([] as any[]);

// here we get current rendered itemSlice
const { renderedItems, itemSlice } = useVirtualOverflowY({
    itemsLengthY: items.length,
    // ...
});

// here we check if we render bottom range of items
useEffect(() => {
    if (itemSlice.topStartIndex + itemSlice.lengthY >= items.length - 4) {
        // load more
        setItems((prev) => [...prev, ...newItems]);
    }
}, [itemSlice.topStartIndex, itemSlice.lengthY]);

utils

All methods here are inside virtualOverflowUtils namespace in react-virtual-overflow/utils. I will not write namespace here below for readability purposes.

Usually react app is static, so you dont need to calc all parents rect (except if it has floating parents, than use default method).
So for this case better use calcVisibleRectOverflowed.

This method will find only parents with overflow style set and calculate clipping only with them. It may boost performance for some cases.

Also if you know all containers with scroll (which you can find with findScrollContainerTopStack) you can calculate directly with calcVisibleRectWithStack.

import { virtualOverflowUtils } from "react-virtual-overflow/lib/utils";

// in component
const [parentsWithOverflow, setParentsWithOverflow] = useState([] as any[]);

useLayoutEffect(() => {
    // we find all elements with overflow once
    const stack = findScrollContainerTopStack(containerRef.current);
    setParentsWithOverflow(stack);
}, []);

const { renderedItems } = useVirtualOverflowY({
    containerRef,
    itemsLengthY,
    itemHeight,
    calcVisibleRect: (el: HTMLElement) => {
        // calculate only by found overflows
        return virtualOverflowUtils.calcVisibleRectWithStack(el, parentsWithOverflow);
    },
    renderItem,
},
    // add overflow stack to deps
    [parentsWithOverflow]
);