Skip to content

virtual scroll: implement scrollTo functions #11498

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy {
}
}

/** Get the offset for the given index. */
getScrollOffsetForIndex() {
throw new Error('Offset location is not supported with autosize.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's say "not currently supported", this sounds like it never will be

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that the scrollToIndex will not be supported for autosize in initial version?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The autosize strategy will probably stay in cdk-experimental a little longer than the fixed size strategy, while we work out some of the bugs. This will be implemented before it goes into cdk

}

/**
* Update the buffer parameters.
* @param minBufferPx The minimum amount of buffer rendered beyond the viewport (in pixels).
Expand Down
5 changes: 5 additions & 0 deletions src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy {
/** @docs-private Implemented as part of VirtualScrollStrategy. */
onRenderedOffsetChanged() { /* no-op */ }

/** Get the offset for the given index. */
getScrollOffsetForIndex(index: number) {
return index * this._itemSize;
}

/** Update the viewport's total content size. */
private _updateTotalContentSize() {
if (!this._viewport) {
Expand Down
3 changes: 3 additions & 0 deletions src/cdk-experimental/scrolling/virtual-scroll-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,7 @@ export interface VirtualScrollStrategy {

/** Called when the offset of the rendered items changed. */
onRenderedOffsetChanged();

/** Get the offset for the given index. */
getScrollOffsetForIndex(index: number);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we may want to put the whole scrollToIndex function as part of the strategy and just have the viewport delegate. While the autosize strategy might not know exactly where the item is, I think we can create an implementation later on that just kind of guesses. It would require the strategy to have full control over what the scroll method does though, not just spitting back an offset.

}
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,15 @@ describe('CdkVirtualScrollViewport', () => {
expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6});
}));

it('should scroll to offset', fakeAsync(() => {
finishInit(fixture);
viewport.scrollToOffset(testComponent.itemSize * 2);
fixture.detectChanges();

expect(viewport.elementRef.nativeElement.scrollTop).toBe(testComponent.itemSize * 2);
expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6});
}));

it('should update viewport as user scrolls down', fakeAsync(() => {
finishInit(fixture);

Expand Down
36 changes: 35 additions & 1 deletion src/cdk-experimental/scrolling/virtual-scroll-viewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {animationFrameScheduler, fromEvent, Observable, Subject} from 'rxjs';
import {sampleTime, take, takeUntil} from 'rxjs/operators';
import {CdkVirtualForOf} from './virtual-for-of';
import {VIRTUAL_SCROLL_STRATEGY, VirtualScrollStrategy} from './virtual-scroll-strategy';
import { supportsSmoothScroll } from '@angular/cdk/platform';


/** Checks if the given ranges are equal. */
Expand Down Expand Up @@ -246,7 +247,40 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
}
}

/** Sets the scroll offset on the viewport. */
/** Scrolls to the offset on the viewport. */
scrollToOffset(offset: number, options = { smooth: false, lazy: false }) {
const viewportElement = this.elementRef.nativeElement;
const top = this.orientation === 'vertical' ? offset : 0;
const left = this.orientation === 'horizontal' ? offset : 0;

let shouldScroll = true;
if (options.lazy) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this logic is quite right because the rendered content may be larger than the viewport.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your right. I changed it to measure the viewport size rather than content.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if lazy scrolling is a common enough use case to be something we want to include in the API. It's pretty easy to implement on top if we just provide the basic functionality

const currentOffset = this.measureScrollOffset();
const currentOffsetEnd = currentOffset + this.getViewportSize();
shouldScroll = offset < currentOffset || offset > currentOffsetEnd;
}

if (shouldScroll) {
if (options.smooth && supportsSmoothScroll()) {
viewportElement.scrollTo({ left, top, behavior: 'smooth' });
} else {
if (this.orientation === 'vertical') {
viewportElement.scrollTop = top;
} else {
viewportElement.scrollLeft = left;
}
}
}
}

/** Scroll the viewport to the specified index. */
scrollToIndex(index: number, options = { smooth: false, lazy: false }) {
const contentSize = this.measureRenderedContentSize();
const offset = this._scrollStrategy.getScrollOffsetForIndex(index);
this.scrollToOffset(offset, options);
}

/** Internal method to set the scroll offset on the viewport. */
setScrollOffset(offset: number) {
// Rather than setting the offset immediately, we batch it up to be applied along with other DOM
// writes during the next change detection cycle.
Expand Down
7 changes: 7 additions & 0 deletions src/cdk/platform/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ export function supportsPassiveEventListeners(): boolean {
return supportsPassiveEvents;
}

/**
* Check whether the browser supports scroll behaviors.
*/
export function supportsSmoothScroll(): boolean {
return 'scrollBehavior' in document.documentElement.style;
}

/** Cached result Set of input types support by the current browser. */
let supportedInputTypes: Set<string>;

Expand Down