@@ -85,6 +85,13 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
85
85
/** The last measured size of the rendered content in the viewport. */
86
86
private _lastRenderedContentOffset : number ;
87
87
88
+ /**
89
+ * The number of consecutive cycles where removing extra items has failed. Failure here means that
90
+ * we estimated how many items we could safely remove, but our estimate turned out to be too much
91
+ * and it wasn't safe to remove that many elements.
92
+ */
93
+ private _removalFailures = 0 ;
94
+
88
95
/**
89
96
* @param minBufferPx The minimum amount of buffer rendered beyond the viewport (in pixels).
90
97
* If the amount of buffer dips below this number, more items will be rendered.
@@ -182,6 +189,8 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
182
189
if ( scrollMagnitude >= viewport . getViewportSize ( ) ) {
183
190
this . _setScrollOffset ( ) ;
184
191
} else {
192
+ // The currently rendered range.
193
+ const renderedRange = viewport . getRenderedRange ( ) ;
185
194
// The number of new items to render on the side the user is scrolling towards. Rather than
186
195
// just filling the underscan space, we actually fill enough to have a buffer size of
187
196
// `addBufferPx`. This gives us a little wiggle room in case our item size estimate is off.
@@ -192,11 +201,13 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
192
201
const overscan = ( scrollDelta < 0 ? endBuffer : startBuffer ) - this . _minBufferPx +
193
202
scrollMagnitude ;
194
203
// 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 ( ) ) ) ;
204
+ // from. If removal has failed in recent cycles we are less aggressive in how much we try to
205
+ // remove.
206
+ const unboundedRemoveItems = Math . floor (
207
+ overscan / this . _averager . getAverageItemSize ( ) / ( this . _removalFailures + 1 ) ) ;
208
+ const removeItems =
209
+ Math . min ( renderedRange . end - renderedRange . start , Math . max ( 0 , unboundedRemoveItems ) ) ;
197
210
198
- // The currently rendered range.
199
- const renderedRange = viewport . getRenderedRange ( ) ;
200
211
// The new range we will tell the viewport to render. We first expand it to include the new
201
212
// items we want rendered, we then contract the opposite side to remove items we no longer
202
213
// want rendered.
@@ -215,19 +226,39 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
215
226
let contentOffset : number ;
216
227
let contentOffsetTo : 'to-start' | 'to-end' ;
217
228
if ( scrollDelta < 0 ) {
218
- const removedSize = viewport . measureRangeSize ( {
229
+ let removedSize = viewport . measureRangeSize ( {
219
230
start : range . end ,
220
231
end : renderedRange . end ,
221
232
} ) ;
222
- contentOffset =
223
- this . _lastRenderedContentOffset + this . _lastRenderedContentSize - removedSize ;
233
+ // Check that we're not removing too much.
234
+ if ( removedSize <= overscan ) {
235
+ contentOffset =
236
+ this . _lastRenderedContentOffset + this . _lastRenderedContentSize - removedSize ;
237
+ this . _removalFailures = 0 ;
238
+ } else {
239
+ // If the removal is more than the overscan can absorb just undo it and record the fact
240
+ // that the removal failed so we can be less aggressive next time.
241
+ range . end = renderedRange . end ;
242
+ contentOffset = this . _lastRenderedContentOffset + this . _lastRenderedContentSize ;
243
+ this . _removalFailures ++ ;
244
+ }
224
245
contentOffsetTo = 'to-end' ;
225
246
} else {
226
247
const removedSize = viewport . measureRangeSize ( {
227
248
start : renderedRange . start ,
228
249
end : range . start ,
229
250
} ) ;
230
- contentOffset = this . _lastRenderedContentOffset + removedSize ;
251
+ // Check that we're not removing too much.
252
+ if ( removedSize <= overscan ) {
253
+ contentOffset = this . _lastRenderedContentOffset + removedSize ;
254
+ this . _removalFailures = 0 ;
255
+ } else {
256
+ // If the removal is more than the overscan can absorb just undo it and record the fact
257
+ // that the removal failed so we can be less aggressive next time.
258
+ range . start = renderedRange . start ;
259
+ contentOffset = this . _lastRenderedContentOffset ;
260
+ this . _removalFailures ++ ;
261
+ }
231
262
contentOffsetTo = 'to-start' ;
232
263
}
233
264
@@ -247,7 +278,7 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
247
278
*/
248
279
private _checkRenderedContentSize ( ) {
249
280
const viewport = this . _viewport ! ;
250
- this . _lastRenderedContentOffset = viewport . measureRenderedContentOffset ( ) ;
281
+ this . _lastRenderedContentOffset = viewport . getOffsetToRenderedContentStart ( ) ! ;
251
282
this . _lastRenderedContentSize = viewport . measureRenderedContentSize ( ) ;
252
283
this . _averager . addSample ( viewport . getRenderedRange ( ) , this . _lastRenderedContentSize ) ;
253
284
this . _updateTotalContentSize ( this . _lastRenderedContentSize ) ;
@@ -267,6 +298,7 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
267
298
viewport . setScrollOffset ( scrollOffset ) ;
268
299
}
269
300
this . _lastScrollOffset = scrollOffset ;
301
+ this . _removalFailures = 0 ;
270
302
271
303
const itemSize = this . _averager . getAverageItemSize ( ) ;
272
304
const firstVisibleIndex =
0 commit comments