Add README

master
Vitaliy Filippov 2018-10-09 14:30:50 +03:00
parent c66f35e25d
commit b8e620d203
2 changed files with 97 additions and 3 deletions

View File

@ -1,15 +1,15 @@
/**
* Virtual scroll driver for dynamic row heights
*
* License: LGPLv3.0+
* (c) Vitaliy Filippov 2018
* License: GNU LGPLv3.0+
* (c) Vitaliy Filippov 2018+
*
* @param props { totalItems, minRowHeight, viewportHeight, scrollTop }
* @param oldState - previous state object
* @param getRenderedItemHeight = (itemIndex) => height
* this function MUST return the height of currently rendered item or 0 if it's not currently rendered
* the returned height MUST be >= props.minRowHeight
* the function MAY cache heights of rendered items if you feel your list to be more responsive
* the function MAY cache heights of rendered items if you want your list to be more responsive
* @returns new state object
* you MUST re-render your list when any state values change
* you MUST preserve all keys in the state object and pass it back via `oldState` on the next run

94
README.md Normal file
View File

@ -0,0 +1,94 @@
# Dynamic Virtual Scroll Driver
Virtual scrolling is a technique for displaying long lists when you render only a small number
of visible items and skip items that are offscreen. You may also have heard about it like
"buffered render" or "windowed render" - it's the same.
There are plenty of virtual scroll implementations for JS.
Some of them are part of a larger UI library (ag-grid, ExtJS and so on),
some of them are more standalone (react-virtualized, react-window, ngx-virtual-scroller, react-dynamic-virtual-list).
However, there is a thing that they all miss: dynamic (and unknown apriori) row heights.
Some implementations allow to set different row heights for items, but you must calculate
all heights before rendering; some allow dynamic row heights, but have bugs and do not really work;
others just force you to use fixed row height. Most implementations are also tied to some specific
UI component or framework and are unusable with other ones.
Good news, everyone: we have a solution!
It is render-agnostic and implemented in this library. Basically, this library only does the maths for you
while letting you render your component yourself. You can use it with React, Angular, pure JS or any other framework
you want to. It works smoothly, does not break built-in browser scrolling and even works on mobile devices.
# Usage
The library exports a single function:
```
import { virtualScrollDriver } from 'dynamic-virtual-scroll';
const newState = virtualScrollDriver(
{ totalItems, minRowHeight, viewportHeight, scrollTop },
oldState,
function getRenderedItemHeight(itemIndex) { ... }
);
```
You must call it after each render and also when the viewport, scroll position or items change.
Description of parameters:
* `totalItems` - total number of items in your list
* `minRowHeight` - minimum item height
* `viewportHeight` - current viewport height (take from DOM)
* `scrollTop` - current scroll position (take from DOM)
* `oldState` - previous state object as returned from previous `virtualScrollDriver()` call
* `getRenderedItemHeight = (itemIndex) => height`:
* this function MUST return the height of currently rendered item or 0 if it's not currently rendered
* the returned height MUST be >= props.minRowHeight
* the function MAY cache heights of rendered items if you want your list to be more responsive
Returned object is `newState`. It contains the render parameters for you and also some internal state variables.
What to do with it:
* you MUST re-render your list when any state values change
* you MUST preserve all keys in the state object and pass it back via `oldState` on the next run
* you MUST base your rendering on the following keys:
* `newState.targetHeight` - height of the 1px wide invisible div you should render in the scroll container
* `newState.topPlaceholderHeight` - height of the first (top) placeholder. omit placeholder if it is 0
* `newState.firstMiddleItem` - first item to be rendered after top placeholder
* `newState.middleItemCount` - item count to be renderer after top placeholder. omit items if it is 0
* `newState.middlePlaceholderHeight` - height of the second (middle) placeholder. omit placeholder if it is 0
* `newState.lastItemCount` - item count to be rendered in the end of the list
# Usage example with React
See `DynamicVirtualScrollExample.js`.
How to test it:
* Clone this repository
* `npm install`
* `npm run build`
* Open `index.html` in your browser
# Algorithm
* Use reasonable fixed minimum row height
* Always render `screen/minHeight` last rows
* Find maximum possible viewport start in units of (item number + % of item)
* Measure average height of last rows
* `avgHeight = max(minHeight, lastRowAvgHeight)`
* `targetHeight = avgHeight*rowCount + headerHeight`
* Total scroll view height will be `targetHeight`
* `scrollPos = targetHeight > offsetHeight ? min(1, scrollTop / (targetHeight - offsetHeight)) : 0`
* First visible item will be `Math.floor(scrollPos*maxPossibleViewportStart)`
* Additional scroll offset will be `itemOffset = (scrollPos*maxPossibleViewportStart - Math.floor(scrollPos*maxPossibleViewportStart))*firstVisibleItemHeight`
* First (top) placeholder height will be `scrollTop-itemOffset`
* Second (middle) placeholder height will be `avgHeight*nodeCount - sum(heights of all rendered rows) - (first placeholder height)`
# License and author
Author: Vitaliy Filippov, 2018+
License: GNU LGPLv3.0 or newer