in js/index.js [203:339]
_redraw() {
let invalidateItems = this._processInvalidation();
const { items, itemTemplate } = this.options;
const { viewport, itemHeights, $topFiller, $bottomFiller, virtualized } = this;
let { indexFirst, indexLast, anchor } = this._state;
if (!invalidateItems && items.length === 0) {
return;
}
/**
* The event indicates the list will start redraw.
* @event ListView#willRedraw
*/
this.trigger('willRedraw');
whileTrue(() => {
let isCompleted = true;
const metricsViewport = viewport.getMetrics();
const visibleTop = metricsViewport.outer.top;
const visibleBot = metricsViewport.outer.bottom;
const listTopCur = this.$topFiller.get(0).getBoundingClientRect().top;
const scrollRatio = metricsViewport.scroll.ratioY;
let renderTop = false;
let renderBot = false;
whileTrue(() => {
const listTop = anchor ? anchor.top - itemHeights.read(anchor.index) : listTopCur;
const targetFirst = virtualized ? itemHeights.lowerBound(visibleTop - listTop) : 0;
const targetLast = virtualized ? Math.min(itemHeights.upperBound(visibleBot - listTop) + 1, items.length) : items.length;
const renderFirst = Math.max(targetFirst - 10, 0);
const renderLast = Math.min(targetLast + 10, items.length);
let renderMore = false;
// Clean up
if (targetFirst >= indexLast || targetLast <= indexFirst || invalidateItems) {
$topFiller.nextUntil($bottomFiller).remove();
indexFirst = indexLast = targetFirst;
if (targetFirst !== targetLast && items.length > 0) {
renderMore = true;
}
if (!anchor) {
const index = Math.round(targetFirst * (1 - scrollRatio) + targetLast * scrollRatio);
const top = listTopCur + itemHeights.read(index);
anchor = { index, top };
}
invalidateItems = false;
} else if (!anchor) {
const index = Math.round(indexFirst * (1 - scrollRatio) + indexLast * scrollRatio);
const top = listTopCur + itemHeights.read(index);
anchor = { index, top };
}
// Render top
if (targetFirst < indexFirst) {
$topFiller.after(items.slice(renderFirst, indexFirst).map(itemTemplate));
$topFiller.nextUntil($bottomFiller).slice(0, indexFirst - renderFirst).each((offset, el) => {
itemHeights.writeSingle(renderFirst + offset, el.getBoundingClientRect().height);
});
indexFirst = renderFirst;
renderMore = renderTop = true;
} else if (renderBot && !renderTop && renderFirst > indexFirst) {
const removal = [];
$topFiller.nextUntil($bottomFiller).slice(0, renderFirst - indexFirst).each((offset, el) => removal.push(el));
$(removal).remove();
indexFirst = renderFirst;
renderMore = true;
}
// Render bottom
if (targetLast > indexLast) {
$bottomFiller.before(items.slice(indexLast, renderLast).map(itemTemplate));
$topFiller.nextUntil($bottomFiller).slice(indexLast - indexFirst).each((offset, el) => {
itemHeights.writeSingle(indexLast + offset, el.getBoundingClientRect().height);
});
indexLast = renderLast;
renderMore = renderBot = true;
} else if (renderTop && !renderBot && renderLast < indexLast) {
const removal = [];
$topFiller.nextUntil($bottomFiller).slice(renderLast - indexFirst).each((offset, el) => removal.push(el));
$(removal).remove();
indexLast = renderLast;
renderMore = true;
}
return renderMore;
});
// Update the padding
if (indexFirst !== this.indexFirst || indexLast !== this.indexLast) {
this._applyPaddings({
paddingTop: itemHeights.read(indexFirst),
paddingBottom: itemHeights.read(items.length) - itemHeights.read(indexLast),
});
}
// Adjust the scroll if it's changed significantly
const listTop = anchor.top - itemHeights.read(anchor.index);
const innerTop = listTop - (listTopCur - metricsViewport.inner.top);
const scrollTop = Math.round(visibleTop - innerTop);
let anchorNew = null;
// Do a second scroll for a middle anchor after the item is rendered
if (anchor.isMiddle) {
const index = anchor.index;
const itemTop = listTopCur + this.itemHeights.read(index);
const itemBot = listTopCur + this.itemHeights.read(index + 1);
anchorNew = {
index,
top: (visibleTop + visibleBot + itemTop - itemBot) / 2,
};
isCompleted = false;
}
if (Math.abs(scrollTop - viewport.getMetrics().scroll.y) >= 1) {
this.viewport.scrollTo({ y: scrollTop });
isCompleted = false;
}
anchor = anchorNew;
return !isCompleted;
});
// Write back the render state
_.extend(this._state, { indexFirst, indexLast, anchor: null });
/**
* The event indicates the list view have completed redraw.
* @event ListView#didRedraw
*/
this.trigger('didRedraw');
}