Skip to content

Commit 461a3a9

Browse files
committed
make sure not to remove too many items
1 parent b98402b commit 461a3a9

File tree

2 files changed

+38
-10
lines changed

2 files changed

+38
-10
lines changed

src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
8585
/** The last measured size of the rendered content in the viewport. */
8686
private _lastRenderedContentOffset: number;
8787

88+
/** The number of consecutive cycles where removing extra items has failed. */
89+
private _removalFailures = 0;
90+
8891
/**
8992
* @param minBufferPx The minimum amount of buffer rendered beyond the viewport (in pixels).
9093
* If the amount of buffer dips below this number, more items will be rendered.
@@ -182,6 +185,8 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
182185
if (scrollMagnitude >= viewport.getViewportSize()) {
183186
this._setScrollOffset();
184187
} else {
188+
// The currently rendered range.
189+
const renderedRange = viewport.getRenderedRange();
185190
// The number of new items to render on the side the user is scrolling towards. Rather than
186191
// just filling the underscan space, we actually fill enough to have a buffer size of
187192
// `addBufferPx`. This gives us a little wiggle room in case our item size estimate is off.
@@ -192,11 +197,12 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
192197
const overscan = (scrollDelta < 0 ? endBuffer : startBuffer) - this._minBufferPx +
193198
scrollMagnitude;
194199
// The number of currently rendered items to remove on the side the user is scrolling away
195-
// from.
196-
const removeItems = Math.max(0, Math.floor(overscan / this._averager.getAverageItemSize()));
200+
// from. If removal has failed in recent cycles we are less aggressive in how much we try to
201+
// remove.
202+
const removeItems = Math.min(renderedRange.end - renderedRange.start, Math.max(0,
203+
Math.floor(
204+
overscan / this._averager.getAverageItemSize() / (this._removalFailures + 1))));
197205

198-
// The currently rendered range.
199-
const renderedRange = viewport.getRenderedRange();
200206
// The new range we will tell the viewport to render. We first expand it to include the new
201207
// items we want rendered, we then contract the opposite side to remove items we no longer
202208
// want rendered.
@@ -215,19 +221,39 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
215221
let contentOffset: number;
216222
let contentOffsetTo: 'to-start' | 'to-end';
217223
if (scrollDelta < 0) {
218-
const removedSize = viewport.measureRangeSize({
224+
let removedSize = viewport.measureRangeSize({
219225
start: range.end,
220226
end: renderedRange.end,
221227
});
222-
contentOffset =
223-
this._lastRenderedContentOffset + this._lastRenderedContentSize - removedSize;
228+
// Check that we're not removing too much.
229+
if (removedSize <= overscan) {
230+
contentOffset =
231+
this._lastRenderedContentOffset + this._lastRenderedContentSize - removedSize;
232+
this._removalFailures = 0;
233+
} else {
234+
// If the removal is more than the overscan can absorb just undo it and record the fact
235+
// that the removal failed so we can be less aggressive next time.
236+
range.end = renderedRange.end;
237+
contentOffset = this._lastRenderedContentOffset + this._lastRenderedContentSize;
238+
this._removalFailures++;
239+
}
224240
contentOffsetTo = 'to-end';
225241
} else {
226242
const removedSize = viewport.measureRangeSize({
227243
start: renderedRange.start,
228244
end: range.start,
229245
});
230-
contentOffset = this._lastRenderedContentOffset + removedSize;
246+
// Check that we're not removing too much.
247+
if (removedSize <= overscan) {
248+
contentOffset = this._lastRenderedContentOffset + removedSize;
249+
this._removalFailures = 0;
250+
} else {
251+
// If the removal is more than the overscan can absorb just undo it and record the fact
252+
// that the removal failed so we can be less aggressive next time.
253+
range.start = renderedRange.start;
254+
contentOffset = this._lastRenderedContentOffset;
255+
this._removalFailures++;
256+
}
231257
contentOffsetTo = 'to-start';
232258
}
233259

@@ -267,6 +293,7 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
267293
viewport.setScrollOffset(scrollOffset);
268294
}
269295
this._lastScrollOffset = scrollOffset;
296+
this._removalFailures = 0;
270297

271298
const itemSize = this._averager.getAverageItemSize();
272299
const firstVisibleIndex =

src/demo-app/virtual-scroll/virtual-scroll-demo.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import {Component, ViewEncapsulation} from '@angular/core';
1717
})
1818
export class VirtualScrollDemo {
1919
fixedSizeData = Array(10000).fill(50);
20-
increasingSizeData = Array(10000).fill(0).map((_, i) => i / 10000 * 300);
21-
decreasingSizeData = Array(10000).fill(0).map((_, i) => (10000 - i) / 10000 * 300);
20+
increasingSizeData = Array(10000).fill(0).map((_, i) => (1 + Math.floor(i / 1000)) * 20);
21+
decreasingSizeData = Array(10000).fill(0)
22+
.map((_, i) => (1 + Math.floor((10000 - i) / 1000)) * 20);
2223
randomData = Array(10000).fill(0).map(() => Math.round(Math.random() * 100));
2324
}

0 commit comments

Comments
 (0)