@@ -122,28 +122,35 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
122
122
this . _viewport = null ;
123
123
}
124
124
125
- /** Implemented as part of VirtualScrollStrategy. */
125
+ /** @docs -private Implemented as part of VirtualScrollStrategy. */
126
126
onContentScrolled ( ) {
127
127
if ( this . _viewport ) {
128
128
this . _updateRenderedContentAfterScroll ( ) ;
129
129
}
130
130
}
131
131
132
- /** Implemented as part of VirtualScrollStrategy. */
132
+ /** @docs -private Implemented as part of VirtualScrollStrategy. */
133
133
onDataLengthChanged ( ) {
134
134
if ( this . _viewport ) {
135
135
// TODO(mmalebra): Do something smarter here.
136
136
this . _setScrollOffset ( ) ;
137
137
}
138
138
}
139
139
140
- /** Implemented as part of VirtualScrollStrategy. */
140
+ /** @docs -private Implemented as part of VirtualScrollStrategy. */
141
141
onContentRendered ( ) {
142
142
if ( this . _viewport ) {
143
143
this . _checkRenderedContentSize ( ) ;
144
144
}
145
145
}
146
146
147
+ /** @docs -private Implemented as part of VirtualScrollStrategy. */
148
+ onRenderedOffsetChanged ( ) {
149
+ if ( this . _viewport ) {
150
+ this . _checkRenderedContentOffset ( ) ;
151
+ }
152
+ }
153
+
147
154
/**
148
155
* Update the buffer parameters.
149
156
* @param minBufferPx The minimum amount of buffer rendered beyond the viewport (in pixels).
@@ -162,13 +169,38 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
162
169
// The current scroll offset.
163
170
const scrollOffset = viewport . measureScrollOffset ( ) ;
164
171
// The delta between the current scroll offset and the previously recorded scroll offset.
165
- const scrollDelta = scrollOffset - this . _lastScrollOffset ;
172
+ let scrollDelta = scrollOffset - this . _lastScrollOffset ;
166
173
// The magnitude of the scroll delta.
167
- const scrollMagnitude = Math . abs ( scrollDelta ) ;
174
+ let scrollMagnitude = Math . abs ( scrollDelta ) ;
175
+
176
+ // The currently rendered range.
177
+ const renderedRange = viewport . getRenderedRange ( ) ;
168
178
169
- // TODO(mmalerba): Record error between actual scroll offset and predicted scroll offset given
170
- // the index of the first rendered element. Fudge the scroll delta to slowly eliminate the error
171
- // as the user scrolls.
179
+ // If we're scrolling toward the top, we need to account for the fact that the predicted amount
180
+ // of content and the actual amount of scrollable space may differ. We address this by slowly
181
+ // correcting the difference on each scroll event.
182
+ let offsetCorrection = 0 ;
183
+ if ( scrollDelta < 0 ) {
184
+ // The content offset we would expect based on the average item size.
185
+ const predictedOffset = renderedRange . start * this . _averager . getAverageItemSize ( ) ;
186
+ // The difference between the predicted size of the unrendered content at the beginning and
187
+ // the actual available space to scroll over. We need to reduce this to zero by the time the
188
+ // user scrolls to the top.
189
+ // - 0 indicates that the predicted size and available space are the same.
190
+ // - A negative number that the predicted size is smaller than the available space.
191
+ // - A positive number indicates the predicted size is larger than the available space
192
+ const offsetDifference = predictedOffset - this . _lastRenderedContentOffset ;
193
+ // The amount of difference to correct during this scroll event. We calculate this as a
194
+ // percentage of the total difference based on the percentage of the distance toward the top
195
+ // that the user scrolled.
196
+ offsetCorrection = Math . round ( offsetDifference *
197
+ Math . max ( 0 , Math . min ( 1 , scrollMagnitude / ( scrollOffset + scrollMagnitude ) ) ) ) ;
198
+
199
+ // Based on the offset correction above, we pretend that the scroll delta was bigger or
200
+ // smaller than it actually was, this way we can start to eliminate the difference.
201
+ scrollDelta = scrollDelta - offsetCorrection ;
202
+ scrollMagnitude = Math . abs ( scrollDelta ) ;
203
+ }
172
204
173
205
// The current amount of buffer past the start of the viewport.
174
206
const startBuffer = this . _lastScrollOffset - this . _lastRenderedContentOffset ;
@@ -190,8 +222,6 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
190
222
if ( scrollMagnitude >= viewport . getViewportSize ( ) ) {
191
223
this . _setScrollOffset ( ) ;
192
224
} else {
193
- // The currently rendered range.
194
- const renderedRange = viewport . getRenderedRange ( ) ;
195
225
// The number of new items to render on the side the user is scrolling towards. Rather than
196
226
// just filling the underscan space, we actually fill enough to have a buffer size of
197
227
// `addBufferPx`. This gives us a little wiggle room in case our item size estimate is off.
@@ -265,8 +295,12 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
265
295
266
296
// Set the range and offset we calculated above.
267
297
viewport . setRenderedRange ( range ) ;
268
- viewport . setRenderedContentOffset ( contentOffset , contentOffsetTo ) ;
298
+ viewport . setRenderedContentOffset ( contentOffset + offsetCorrection , contentOffsetTo ) ;
269
299
}
300
+ } else if ( offsetCorrection ) {
301
+ // Even if the rendered range didn't change, we may still need to adjust the content offset to
302
+ // simulate scrolling slightly slower or faster than the user actually scrolled.
303
+ viewport . setRenderedContentOffset ( this . _lastRenderedContentOffset + offsetCorrection ) ;
270
304
}
271
305
272
306
// Save the scroll offset to be compared to the new value on the next scroll event.
@@ -279,12 +313,17 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
279
313
*/
280
314
private _checkRenderedContentSize ( ) {
281
315
const viewport = this . _viewport ! ;
282
- this . _lastRenderedContentOffset = viewport . getOffsetToRenderedContentStart ( ) ! ;
283
316
this . _lastRenderedContentSize = viewport . measureRenderedContentSize ( ) ;
284
317
this . _averager . addSample ( viewport . getRenderedRange ( ) , this . _lastRenderedContentSize ) ;
285
318
this . _updateTotalContentSize ( this . _lastRenderedContentSize ) ;
286
319
}
287
320
321
+ /** Checks the currently rendered content offset and saves the value for later use. */
322
+ private _checkRenderedContentOffset ( ) {
323
+ const viewport = this . _viewport ! ;
324
+ this . _lastRenderedContentOffset = viewport . getOffsetToRenderedContentStart ( ) ! ;
325
+ }
326
+
288
327
/**
289
328
* Sets the scroll offset and renders the content we estimate should be shown at that point.
290
329
* @param scrollOffset The offset to jump to. If not specified the scroll offset will not be
0 commit comments