Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
Vitaliy Filippov | 8567a14027 | |
Vitaliy Filippov | f530c8301c | |
Vitaliy Filippov | ea707b2c08 | |
Vitaliy Filippov | ba46a65559 | |
Vitaliy Filippov | 43f9864c13 | |
Vitaliy Filippov | 24b12584d0 | |
Vitaliy Filippov | d6dc215d06 | |
Vitaliy Filippov | b7df500935 |
|
@ -62,7 +62,7 @@ export function virtualScrollDriver(props, oldState, getRenderedItemHeight)
|
||||||
if (!lastItemSize)
|
if (!lastItemSize)
|
||||||
{
|
{
|
||||||
// Some required items in the end are missing
|
// Some required items in the end are missing
|
||||||
return newState;
|
lastItemSize = 0;
|
||||||
}
|
}
|
||||||
lastItemsHeight += lastItemSize < props.minRowHeight ? props.minRowHeight : lastItemSize;
|
lastItemsHeight += lastItemSize < props.minRowHeight ? props.minRowHeight : lastItemSize;
|
||||||
lastVisibleItems++;
|
lastVisibleItems++;
|
||||||
|
|
|
@ -5,12 +5,12 @@ of visible items and skip items that are offscreen. You may also have heard abou
|
||||||
"buffered render" or "windowed render" - it's the same.
|
"buffered render" or "windowed render" - it's the same.
|
||||||
|
|
||||||
There are plenty of virtual scroll implementations for JS.
|
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 part of a larger UI library (ag-grid, ExtJS and so on), some of them are more
|
||||||
some of them are more standalone (react-virtualized, react-window, ngx-virtual-scroller, react-dynamic-virtual-list).
|
standalone (react-virtualized, react-window, ngx-virtual-scroller, ngx-ui-scroll, react-dynamic-virtual-list).
|
||||||
|
|
||||||
However, there is a thing that they all miss: dynamic (and unknown apriori) row heights.
|
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
|
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;
|
all heights before rendering; some allow dynamic row heights, but have bugs and act weird or don't really work;
|
||||||
others just force you to use fixed row height. Most implementations are also tied to some specific
|
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.
|
UI component or framework and are unusable with other ones.
|
||||||
|
|
||||||
|
|
|
@ -36,10 +36,22 @@ export class VirtualScrollList extends React.Component
|
||||||
|
|
||||||
setItemRef = []
|
setItemRef = []
|
||||||
itemRefs = []
|
itemRefs = []
|
||||||
|
itemRefCount = []
|
||||||
|
|
||||||
makeRef(i)
|
makeRef(i)
|
||||||
{
|
{
|
||||||
this.setItemRef[i] = (e) => this.itemRefs[i] = e;
|
this.setItemRef[i] = (e) =>
|
||||||
|
{
|
||||||
|
// If the new row instance is mounted before unmouting the old one,
|
||||||
|
// we get called 2 times in wrong order: first with the new instance,
|
||||||
|
// then with null telling us that the old one is unmounted.
|
||||||
|
// We track reference count to workaround it.
|
||||||
|
this.itemRefCount[i] = (this.itemRefCount[i]||0) + (e ? 1 : -1);
|
||||||
|
if (e || !this.itemRefCount[i])
|
||||||
|
{
|
||||||
|
this.itemRefs[i] = e;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItems(start, count, is_end)
|
renderItems(start, count, is_end)
|
||||||
|
@ -63,9 +75,10 @@ export class VirtualScrollList extends React.Component
|
||||||
render()
|
render()
|
||||||
{
|
{
|
||||||
if (this.state.totalItems && this.props.totalItems != this.state.totalItems &&
|
if (this.state.totalItems && this.props.totalItems != this.state.totalItems &&
|
||||||
this.state.scrollTimes <= 0)
|
this.state.scrollTimes <= 0 && this.viewport && this.viewport.offsetParent)
|
||||||
{
|
{
|
||||||
// Automatically preserve scroll position when item count changes
|
// Automatically preserve scroll position when item count changes...
|
||||||
|
// But only when the list is on-screen! We'll end up with an infinite update loop if it's off-screen.
|
||||||
this.state.scrollTo = this.getItemScrollPos();
|
this.state.scrollTo = this.getItemScrollPos();
|
||||||
this.state.scrollTimes = 2;
|
this.state.scrollTimes = 2;
|
||||||
}
|
}
|
||||||
|
@ -86,7 +99,7 @@ export class VirtualScrollList extends React.Component
|
||||||
onScroll={this.onScroll}>
|
onScroll={this.onScroll}>
|
||||||
{this.props.header}
|
{this.props.header}
|
||||||
{this.state.targetHeight > 0
|
{this.state.targetHeight > 0
|
||||||
? <div key="target" style={{position: 'absolute', left: '-5px', width: '1px', height: this.state.targetHeight+'px'}}></div>
|
? <div key="target" style={{position: 'absolute', top: 0, left: '-5px', width: '1px', height: this.state.targetHeight+'px'}}></div>
|
||||||
: null}
|
: null}
|
||||||
{this.state.topPlaceholderHeight
|
{this.state.topPlaceholderHeight
|
||||||
? <div style={{height: this.state.topPlaceholderHeight+'px'}} key="top"></div>
|
? <div style={{height: this.state.topPlaceholderHeight+'px'}} key="top"></div>
|
||||||
|
@ -111,7 +124,10 @@ export class VirtualScrollList extends React.Component
|
||||||
const e = ReactDOM.findDOMNode(this.itemRefs[index]);
|
const e = ReactDOM.findDOMNode(this.itemRefs[index]);
|
||||||
if (e)
|
if (e)
|
||||||
{
|
{
|
||||||
return e.getBoundingClientRect().height;
|
// MSIE sometimes manages to report non-integer element heights for elements of an integer height...
|
||||||
|
// Non-integer element sizes are allowed in getBoundingClientRect, one notable example of them
|
||||||
|
// are collapsed table borders. But we still ignore less than 1/100 of a pixel difference.
|
||||||
|
return Math.round(e.getBoundingClientRect().height*100)/100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -120,12 +136,17 @@ export class VirtualScrollList extends React.Component
|
||||||
onScroll = () =>
|
onScroll = () =>
|
||||||
{
|
{
|
||||||
this.driver();
|
this.driver();
|
||||||
|
if (this.props.onScroll)
|
||||||
|
{
|
||||||
|
this.props.onScroll(this.viewport);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate = () =>
|
componentDidUpdate = () =>
|
||||||
{
|
{
|
||||||
let changed = this.driver();
|
let changed = this.driver();
|
||||||
if (!changed && this.state.scrollTimes > 0 && this.props.totalItems > 0)
|
if (!changed && this.state.scrollTimes > 0 && this.props.totalItems > 0 &&
|
||||||
|
this.viewport && this.viewport.offsetParent)
|
||||||
{
|
{
|
||||||
// FIXME: It would be better to find a way to put this logic back into virtual-scroll-driver
|
// FIXME: It would be better to find a way to put this logic back into virtual-scroll-driver
|
||||||
let pos = this.state.scrollTo;
|
let pos = this.state.scrollTo;
|
||||||
|
@ -181,6 +202,11 @@ export class VirtualScrollList extends React.Component
|
||||||
|
|
||||||
driver = () =>
|
driver = () =>
|
||||||
{
|
{
|
||||||
|
if (!this.viewport || !this.viewport.offsetParent)
|
||||||
|
{
|
||||||
|
// Fool tolerance - do nothing if we are hidden
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const newState = virtualScrollDriver(
|
const newState = virtualScrollDriver(
|
||||||
{
|
{
|
||||||
totalItems: this.props.totalItems,
|
totalItems: this.props.totalItems,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "dynamic-virtual-scroll",
|
"name": "dynamic-virtual-scroll",
|
||||||
"version": "1.0.8",
|
"version": "1.0.15",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Vitaliy Filippov",
|
"name": "Vitaliy Filippov",
|
||||||
"email": "vitalif@yourcmc.ru",
|
"email": "vitalif@yourcmc.ru",
|
||||||
|
|
Loading…
Reference in New Issue