Compare commits

...

8 Commits

4 changed files with 37 additions and 11 deletions

View File

@ -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++;

View File

@ -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.

View File

@ -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,

View File

@ -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",