import { useCallback, useMemo, useRef, useState } from 'react'; type Options = { /** высота одного элемента в px (фиксированная) */ itemHeight: number; /** высота контейнера в px (видимая область) */ containerHeight: number; /** сколько элементов рендерить дополнительно сверху/снизу */ overscan?: number; }; export function useVirtualList(items: T[], options: Options) { const { itemHeight, containerHeight, overscan = 5 } = options; const [scrollTop, setScrollTop] = useState(0); const ref = useRef(null); const totalHeight = items.length * itemHeight; const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan); const endIndex = Math.min(items.length, Math.ceil((scrollTop + containerHeight) / itemHeight) + overscan); const visibleItems = useMemo( () => items.slice(startIndex, endIndex).map((item, i) => ({ item, index: startIndex + i, })), [items, startIndex, endIndex], ); const offsetY = startIndex * itemHeight; const onScroll = useCallback((e: React.UIEvent) => { setScrollTop(e.currentTarget.scrollTop); }, []); return { ref, onScroll, visibleItems, totalHeight, offsetY, }; }