Skip to content

Commit 130cb75

Browse files
committed
feat(virtual-scroll): change fixed strategy to use min & max buffer
1 parent c5cfede commit 130cb75

File tree

7 files changed

+134
-90
lines changed

7 files changed

+134
-90
lines changed

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

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
8383
private _minBufferPx: number;
8484

8585
/** The number of buffer items to render beyond the edge of the viewport (in pixels). */
86-
private _addBufferPx: number;
86+
private _maxBufferPx: number;
8787

8888
/** The estimator used to estimate the size of unseen items. */
8989
private _averager: ItemSizeAverager;
@@ -107,14 +107,14 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
107107
/**
108108
* @param minBufferPx The minimum amount of buffer rendered beyond the viewport (in pixels).
109109
* If the amount of buffer dips below this number, more items will be rendered.
110-
* @param addBufferPx The number of pixels worth of buffer to shoot for when rendering new items.
110+
* @param maxBufferPx The number of pixels worth of buffer to shoot for when rendering new items.
111111
* If the actual amount turns out to be less it will not necessarily trigger an additional
112112
* rendering cycle (as long as the amount of buffer is still greater than `minBufferPx`).
113113
* @param averager The averager used to estimate the size of unseen items.
114114
*/
115-
constructor(minBufferPx: number, addBufferPx: number, averager = new ItemSizeAverager()) {
115+
constructor(minBufferPx: number, maxBufferPx: number, averager = new ItemSizeAverager()) {
116116
this._minBufferPx = minBufferPx;
117-
this._addBufferPx = addBufferPx;
117+
this._maxBufferPx = maxBufferPx;
118118
this._averager = averager;
119119
}
120120

@@ -172,12 +172,12 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
172172
/**
173173
* Update the buffer parameters.
174174
* @param minBufferPx The minimum amount of buffer rendered beyond the viewport (in pixels).
175-
* @param addBufferPx The number of buffer items to render beyond the edge of the viewport (in
175+
* @param maxBufferPx The number of buffer items to render beyond the edge of the viewport (in
176176
* pixels).
177177
*/
178-
updateBufferSize(minBufferPx: number, addBufferPx: number) {
178+
updateBufferSize(minBufferPx: number, maxBufferPx: number) {
179179
this._minBufferPx = minBufferPx;
180-
this._addBufferPx = addBufferPx;
180+
this._maxBufferPx = maxBufferPx;
181181
}
182182

183183
/** Update the rendered content after the user scrolls. */
@@ -242,8 +242,8 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
242242
} else {
243243
// The number of new items to render on the side the user is scrolling towards. Rather than
244244
// just filling the underscan space, we actually fill enough to have a buffer size of
245-
// `addBufferPx`. This gives us a little wiggle room in case our item size estimate is off.
246-
const addItems = Math.max(0, Math.ceil((underscan - this._minBufferPx + this._addBufferPx) /
245+
// `maxBufferPx`. This gives us a little wiggle room in case our item size estimate is off.
246+
const addItems = Math.max(0, Math.ceil((underscan - this._minBufferPx + this._maxBufferPx) /
247247
this._averager.getAverageItemSize()));
248248
// The amount of filled space beyond what is necessary on the side the user is scrolling
249249
// away from.
@@ -361,7 +361,7 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
361361
const itemSize = this._averager.getAverageItemSize();
362362
const firstVisibleIndex =
363363
Math.min(viewport.getDataLength() - 1, Math.floor(scrollOffset / itemSize));
364-
const bufferSize = Math.ceil(this._addBufferPx / itemSize);
364+
const bufferSize = Math.ceil(this._maxBufferPx / itemSize);
365365
const range = this._expandRange(
366366
this._getVisibleRangeForIndex(firstVisibleIndex), bufferSize, bufferSize);
367367

@@ -456,14 +456,14 @@ export class CdkAutoSizeVirtualScroll implements OnChanges {
456456
* Defaults to 200px.
457457
*/
458458
@Input()
459-
get addBufferPx(): number { return this._addBufferPx; }
460-
set addBufferPx(value: number) { this._addBufferPx = coerceNumberProperty(value); }
461-
_addBufferPx = 200;
459+
get maxBufferPx(): number { return this._maxBufferPx; }
460+
set maxBufferPx(value: number) { this._maxBufferPx = coerceNumberProperty(value); }
461+
_maxBufferPx = 200;
462462

463463
/** The scroll strategy used by this directive. */
464-
_scrollStrategy = new AutoSizeVirtualScrollStrategy(this.minBufferPx, this.addBufferPx);
464+
_scrollStrategy = new AutoSizeVirtualScrollStrategy(this.minBufferPx, this.maxBufferPx);
465465

466466
ngOnChanges() {
467-
this._scrollStrategy.updateBufferSize(this.minBufferPx, this.addBufferPx);
467+
this._scrollStrategy.updateBufferSize(this.minBufferPx, this.maxBufferPx);
468468
}
469469
}

src/cdk-experimental/scrolling/scrolling.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@ This can be added to your viewport by using the `autosize` directive.
1111
</cdk-virtual-scroll-viewport>
1212
```
1313

14-
The `autosize` strategy is configured through two inputs: `minBufferPx` and `addBufferPx`.
14+
The `autosize` strategy is configured through two inputs: `minBufferPx` and `maxBufferPx`.
1515

1616
**`minBufferPx`** determines the minimum space outside virtual scrolling viewport that will be
1717
filled with content. Increasing this will increase the amount of content a user will see before more
1818
content must be rendered. However, too large a value will cause more content to be rendered than is
1919
necessary.
2020

21-
**`addBufferPx`** determines the amount of content that will be added incrementally as the viewport
21+
**`maxBufferPx`** determines the amount of content that will be added incrementally as the viewport
2222
is scrolled. This should be greater than the size of `minBufferPx` so that one "render" is needed at
2323
a time.
2424

2525
```html
26-
<cdk-virtual-scroll-viewport autosize minBufferPx="50" addBufferPx="100">
26+
<cdk-virtual-scroll-viewport autosize minBufferPx="50" maxBufferPx="100">
2727
...
2828
</cdk-virtual-scroll-viewport>
2929
```

src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ function finishInit(fixture: ComponentFixture<any>) {
6161
@Component({
6262
template: `
6363
<cdk-virtual-scroll-viewport
64-
autosize [minBufferPx]="minBufferSize" [addBufferPx]="addBufferSize"
64+
autosize [minBufferPx]="minBufferPx" [maxBufferPx]="maxBufferPx"
6565
[orientation]="orientation" [style.height.px]="viewportHeight"
6666
[style.width.px]="viewportWidth">
6767
<div class="item" *cdkVirtualFor="let size of items; let i = index" [style.height.px]="size"
@@ -88,8 +88,8 @@ class AutoSizeVirtualScroll {
8888
@Input() orientation = 'vertical';
8989
@Input() viewportSize = 200;
9090
@Input() viewportCrossSize = 100;
91-
@Input() minBufferSize = 0;
92-
@Input() addBufferSize = 0;
91+
@Input() minBufferPx = 0;
92+
@Input() maxBufferPx = 0;
9393
@Input() items = Array(10).fill(50);
9494

9595
get viewportWidth() {

src/cdk/scrolling/fixed-size-virtual-scroll.ts

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
*/
88

99
import {coerceNumberProperty} from '@angular/cdk/coercion';
10-
import {ListRange} from '@angular/cdk/collections';
1110
import {Directive, forwardRef, Input, OnChanges} from '@angular/core';
1211
import {Observable, Subject} from 'rxjs';
1312
import {distinctUntilChanged} from 'rxjs/operators';
@@ -28,16 +27,21 @@ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy {
2827
/** The size of the items in the virtually scrolling list. */
2928
private _itemSize: number;
3029

31-
/** The number of buffer items to render beyond the edge of the viewport. */
32-
private _bufferSize: number;
30+
/** The minimum amount of buffer rendered beyond the viewport (in pixels). */
31+
private _minBufferPx: number;
32+
33+
/** The number of buffer items to render beyond the edge of the viewport (in pixels). */
34+
private _maxBufferPx: number;
3335

3436
/**
3537
* @param itemSize The size of the items in the virtually scrolling list.
36-
* @param bufferSize The number of buffer items to render beyond the edge of the viewport.
38+
* @param minBufferPx The minimum amount of buffer (in pixels) before needing to render more
39+
* @param maxBufferPx The amount of buffer (in pixels) to render when rendering more.
3740
*/
38-
constructor(itemSize: number, bufferSize: number) {
41+
constructor(itemSize: number, minBufferPx: number, maxBufferPx: number) {
3942
this._itemSize = itemSize;
40-
this._bufferSize = bufferSize;
43+
this._minBufferPx = minBufferPx;
44+
this._maxBufferPx = maxBufferPx;
4145
}
4246

4347
/**
@@ -59,11 +63,13 @@ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy {
5963
/**
6064
* Update the item size and buffer size.
6165
* @param itemSize The size of the items in the virtually scrolling list.
62-
* @param bufferSize he number of buffer items to render beyond the edge of the viewport.
66+
* @param minBufferPx The minimum amount of buffer (in pixels) before needing to render more
67+
* @param maxBufferPx The amount of buffer (in pixels) to render when rendering more.
6368
*/
64-
updateItemAndBufferSize(itemSize: number, bufferSize: number) {
69+
updateItemAndBufferSize(itemSize: number, minBufferPx: number, maxBufferPx: number) {
6570
this._itemSize = itemSize;
66-
this._bufferSize = bufferSize;
71+
this._minBufferPx = minBufferPx;
72+
this._maxBufferPx = maxBufferPx;
6773
this._updateTotalContentSize();
6874
this._updateRenderedRange();
6975
}
@@ -112,34 +118,32 @@ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy {
112118
}
113119

114120
const scrollOffset = this._viewport.measureScrollOffset();
115-
const firstVisibleIndex = Math.floor(scrollOffset / this._itemSize);
116-
const firstItemRemainder = scrollOffset % this._itemSize;
117-
const range = this._expandRange(
118-
{start: firstVisibleIndex, end: firstVisibleIndex},
119-
this._bufferSize,
120-
Math.ceil((this._viewport.getViewportSize() + firstItemRemainder) / this._itemSize) +
121-
this._bufferSize);
122-
this._viewport.setRenderedRange(range);
123-
this._viewport.setRenderedContentOffset(this._itemSize * range.start);
124-
125-
this._scrolledIndexChange.next(firstVisibleIndex);
126-
}
127-
128-
/**
129-
* Expand the given range by the given amount in either direction.
130-
* @param range The range to expand
131-
* @param expandStart The number of items to expand the start of the range by.
132-
* @param expandEnd The number of items to expand the end of the range by.
133-
* @return The expanded range.
134-
*/
135-
private _expandRange(range: ListRange, expandStart: number, expandEnd: number): ListRange {
136-
if (!this._viewport) {
137-
return {...range};
121+
const firstVisibleIndex = scrollOffset / this._itemSize;
122+
const range = {...this._viewport.getRenderedRange()};
123+
124+
const startBuffer = scrollOffset - range.start * this._itemSize;
125+
if (startBuffer < this._minBufferPx && range.start != 0) {
126+
const expandStart = Math.ceil((this._maxBufferPx - startBuffer) / this._itemSize);
127+
range.start = Math.max(0, range.start - expandStart);
128+
range.end = Math.min(this._viewport.getDataLength(),
129+
Math.ceil(firstVisibleIndex +
130+
(this._viewport.getViewportSize() + this._minBufferPx) / this._itemSize));
131+
} else {
132+
const endBuffer =
133+
range.end * this._itemSize - (scrollOffset + this._viewport.getViewportSize());
134+
if (endBuffer < this._minBufferPx && range.end != this._viewport.getDataLength()) {
135+
const expandEnd = Math.ceil((this._maxBufferPx - endBuffer) / this._itemSize);
136+
if (expandEnd > 0) {
137+
range.end = Math.min(this._viewport.getDataLength(), range.end + expandEnd);
138+
range.start = Math.max(0,
139+
Math.floor(firstVisibleIndex - this._minBufferPx / this._itemSize));
140+
}
141+
}
138142
}
139143

140-
const start = Math.max(0, range.start - expandStart);
141-
const end = Math.min(this._viewport.getDataLength(), range.end + expandEnd);
142-
return {start, end};
144+
this._viewport.setRenderedRange(range);
145+
this._viewport.setRenderedContentOffset(this._itemSize * range.start);
146+
this._scrolledIndexChange.next(Math.floor(firstVisibleIndex));
143147
}
144148
}
145149

@@ -172,18 +176,27 @@ export class CdkFixedSizeVirtualScroll implements OnChanges {
172176
_itemSize = 20;
173177

174178
/**
175-
* The number of extra elements to render on either side of the scrolling viewport.
176-
* Defaults to 5 elements.
179+
* The minimum amount of buffer rendered beyond the viewport (in pixels).
180+
* If the amount of buffer dips below this number, more items will be rendered. Defaults to 100px.
181+
*/
182+
@Input()
183+
get minBufferPx(): number { return this._minBufferPx; }
184+
set minBufferPx(value: number) { this._minBufferPx = coerceNumberProperty(value); }
185+
_minBufferPx = 100;
186+
187+
/**
188+
* The number of pixels worth of buffer to render for when rendering new items. Defaults to 200px.
177189
*/
178190
@Input()
179-
get bufferSize(): number { return this._bufferSize; }
180-
set bufferSize(value: number) { this._bufferSize = coerceNumberProperty(value); }
181-
_bufferSize = 5;
191+
get maxBufferPx(): number { return this._maxBufferPx; }
192+
set maxBufferPx(value: number) { this._maxBufferPx = coerceNumberProperty(value); }
193+
_maxBufferPx = 200;
182194

183195
/** The scroll strategy used by this directive. */
184-
_scrollStrategy = new FixedSizeVirtualScrollStrategy(this.itemSize, this.bufferSize);
196+
_scrollStrategy =
197+
new FixedSizeVirtualScrollStrategy(this.itemSize, this.minBufferPx, this.maxBufferPx);
185198

186199
ngOnChanges() {
187-
this._scrollStrategy.updateItemAndBufferSize(this.itemSize, this.bufferSize);
200+
this._scrollStrategy.updateItemAndBufferSize(this.itemSize, this.minBufferPx, this.maxBufferPx);
188201
}
189202
}

src/cdk/scrolling/scrolling.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,22 @@ that it allows for better performance, since items do not need to be measured as
9494
</cdk-virtual-scroll-viewport>
9595
```
9696

97-
The fixed size strategy also supports setting the buffer size, i.e. the number of items rendered
98-
beyond the edge of the viewport. This can be adjusted by setting the `bufferSize` input. If
99-
`bufferSize` is not specified it defaults to 5 items.
97+
The fixed size strategy also supports setting a couple buffer parameters that determine how much
98+
extra content is rendered beyond what is visible in the viewport. The first of these parameters is
99+
`minBufferPx`. The `minBufferPx` is the minimum amount of content buffer (in pixels) that the
100+
viewport must render. If the viewport ever detects that there is less buffered content it will
101+
immediately render more. The second buffer parameter is `maxBufferPx`. This tells the viewport how
102+
much buffer space to render back up to when it detects that more buffer is required.
103+
104+
The interaction of these two buffer parameters can be best illustrated with an example. Supposed
105+
that we have the following parameters: `itemSize = 50`, `minBufferPx = 100`, `maxBufferPx = 250`. As
106+
the user is scrolling through the content the viewport detects that there is only `90px` of buffer
107+
remaining. Since this is below `minBufferPx` the viewport must render more buffer. It must render at
108+
least enough buffer to get back to `maxBufferPx`. In this case, it renders 4 items (an additional
109+
`200px`) to bring the total buffer size to `290px`, back above `maxBufferPx`.
100110

101111
```html
102-
<cdk-virtual-scroll-viewport itemSize="50" bufferSize="1">
112+
<cdk-virtual-scroll-viewport itemSize="50" minBufferPx="100" maxBufferPx="250">
103113
...
104114
</cdk-virtual-scroll-viewport>
105115
```

0 commit comments

Comments
 (0)