Skip to content

Commit 0303207

Browse files
committed
fix(cdk/scrolling): scrollToIndex combined with cdkVirtualScrollingEement can result in wrong scroll offset
Fixes a bug when using cdkVirtualScrollingElement and there is some space between the scrolling element and the viewport the scroll to index function doesn't take into account the viewport offset from the scrolling container. Fixes #33063
1 parent b04751c commit 0303207

4 files changed

Lines changed: 40 additions & 10 deletions

File tree

goldens/cdk/scrolling/index.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On
202202
scrollable: CdkVirtualScrollable;
203203
readonly scrolledIndexChange: Observable<number>;
204204
scrollToIndex(index: number, behavior?: ScrollBehavior): void;
205-
scrollToOffset(offset: number, behavior?: ScrollBehavior): void;
205+
scrollToOffset(offset: number, behavior?: ScrollBehavior, relativeTo?: 'viewport' | 'scrollingContainer'): void;
206206
setRenderedContentOffset(offset: number, to?: 'to-start' | 'to-end'): void;
207207
setRenderedRange(range: ListRange): void;
208208
setTotalContentSize(size: number): void;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy {
104104
*/
105105
scrollToIndex(index: number, behavior: ScrollBehavior): void {
106106
if (this._viewport) {
107-
this._viewport.scrollToOffset(index * this._itemSize, behavior);
107+
this._viewport.scrollToOffset(index * this._itemSize, behavior, 'viewport');
108108
}
109109
}
110110

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

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,16 +1081,27 @@ describe('CdkVirtualScrollViewport', () => {
10811081
.toBe(50);
10821082
}));
10831083

1084-
it('should measure scroll offset with custom scrolling element', fakeAsync(() => {
1084+
it('should scroll to offset relative to scrolling container', fakeAsync(() => {
10851085
finishInit(fixture);
1086-
triggerScroll(viewport, 100);
1086+
triggerScroll(viewport, 100, 'scrollingContainer');
10871087
fixture.detectChanges();
10881088
flush();
10891089

10901090
expect(viewport.measureScrollOffset('top'))
1091-
.withContext('should be 50 (actual scroll offset - viewport offset)')
1091+
.withContext('should be 50 (scrolling container offset)')
10921092
.toBe(50);
10931093
}));
1094+
1095+
it('should scroll to offset relative to viewport', fakeAsync(() => {
1096+
finishInit(fixture);
1097+
triggerScroll(viewport, 100, 'viewport');
1098+
fixture.detectChanges();
1099+
flush();
1100+
1101+
expect(viewport.measureScrollOffset('top'))
1102+
.withContext('should be 100 (viewport offset)')
1103+
.toBe(100);
1104+
}));
10941105
});
10951106

10961107
describe('with scrollable window', () => {
@@ -1143,9 +1154,13 @@ function finishInit(fixture: ComponentFixture<any>) {
11431154
}
11441155

11451156
/** Trigger a scroll event on the viewport (optionally setting a new scroll offset). */
1146-
function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number) {
1157+
function triggerScroll(
1158+
viewport: CdkVirtualScrollViewport,
1159+
offset?: number,
1160+
relativeTo?: 'viewport' | 'scrollingContainer',
1161+
) {
11471162
if (offset !== undefined) {
1148-
viewport.scrollToOffset(offset);
1163+
viewport.scrollToOffset(offset, 'auto', relativeTo);
11491164
}
11501165
dispatchFakeEvent(viewport.scrollable!.getElementRef().nativeElement, 'scroll');
11511166
tick(16); // flush animation frame

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,13 +411,28 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On
411411
* direction, this would be the equivalent of setting a fictional `scrollRight` property.
412412
* @param offset The offset to scroll to.
413413
* @param behavior The ScrollBehavior to use when scrolling. Default is behavior is `auto`.
414+
* @param relativeTo The start point of the offset. Default is `scrollingContainer`.
414415
*/
415-
scrollToOffset(offset: number, behavior: ScrollBehavior = 'auto') {
416+
scrollToOffset(
417+
offset: number,
418+
behavior: ScrollBehavior = 'auto',
419+
relativeTo: 'viewport' | 'scrollingContainer' = 'scrollingContainer',
420+
) {
416421
const options: ExtendedScrollToOptions = {behavior};
417422
if (this.orientation === 'horizontal') {
418-
options.start = offset;
423+
if (relativeTo === 'scrollingContainer') {
424+
options.start = offset;
425+
} else {
426+
const viewportOffset = this.measureViewportOffset('start');
427+
options.start = viewportOffset + offset;
428+
}
419429
} else {
420-
options.top = offset;
430+
if (relativeTo === 'scrollingContainer') {
431+
options.top = offset;
432+
} else {
433+
const viewportOffset = this.measureViewportOffset('top');
434+
options.top = viewportOffset + offset;
435+
}
421436
}
422437
this.scrollable.scrollTo(options);
423438
}

0 commit comments

Comments
 (0)