使用 cdk-virtual-scroll 滚动到底部 (Angular 8)
Scroll to bottom with cdk-virtual-scroll (Angular 8)
目标
显示消息列表并在收到新消息时滚动到底部,即使我在顶部也是如此。即使有不同高度的元素,我也想完全滚动到底部。
问题
使用虚拟滚动,我必须设置 [itemSize]
属性,但对我来说它不是一个静态值:
- 当一条消息对于一行来说太长时,它会分成多行,因此它的高度会发生变化。
- 我有不同高度的不同类型的消息(例如系统消息)。
此外,我正在使用 ng-content
从父级插入一个按钮来加载以前的消息。我看到的是,当 _scrollToBottom
被调用时,它没有把我带到底部,而是把我带到了更高一点。我怀疑这是因为虚拟滚动中元素的高度不同。
我已经从 Angular 阅读了这个自动调整滚动策略问题:https://github.com/angular/components/issues/10113; 但我不确定这是否能解决我的问题。
任何关于我能做什么的想法都将受到欢迎。
测试
Codesandbox: https://codesandbox.io/s/angular-virtual-scroll-biwn6
- 加载消息后,向上滚动。
- 发送消息。 (加载新消息时,虚拟滚动不再滚动到底部,而是停在更高一点)
- 重复
有错误的视频:https://gofile.io/d/8NG9HD
解决方案
Gourav Garg 给出的解决方案有效。只需执行两次滚动功能即可。
我现在正在这样做:
private _scrollToBottom() {
setTimeout(() => {
this.virtualScrollViewport.scrollTo({
bottom: 0,
behavior: 'auto',
});
}, 0);
setTimeout(() => {
this.virtualScrollViewport.scrollTo({
bottom: 0,
behavior: 'auto',
});
}, 50);
}
我认为它不是很优雅,但工作正常。
如果你用的是cdk > 7就可以用
this.virtualScrollViewport.scrollToIndex(messages.length-1);
因为这将移至最后一项的顶部。您需要为该项目调用 scrollIntoView。
this.virtualScrollViewport.scrollToIndex(this.numbers.length - 1);
setTimeout(() => {
const items = document.getElementsByClassName("list-item");
items[items.length - 1].scrollIntoView();
}, 10);
<cdk-virtual-scroll-viewport #virtualScroll style="height: 500px" itemSize="90">
<ng-container *cdkVirtualFor="let n of numbers">
<li class="list-item"> {{n}} </li>
</ng-container>
</cdk-virtual-scroll-viewport>
我已经更新了你的sandbox
使用可以使用scrollIntoView()
滚动到virtualScrollViewport
的底部
this.virtualScrollViewport.scrollIntoView(false);
参考https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
有 cdk scroll 的替代方法。
这是一个通用版本的代码,用于滚动到给定的 HTML 元素。它可以用作 angular 中的服务功能,也可以用作 javascript 中的功能。
scroll(el: HTMLElement, behaviour: any = "smooth", block: any = "start", inline: any = "nearest") {
el.scrollIntoView({ behavior: behaviour, block: block, inline: inline })
}
样本HTML:
<div class="scroll-to-top" [ngClass]="{'show-scrollTop': windowScrolled}">
Back to top<button mat-button mat-icon-button (click)="scrollToTop()">
<mat-icon>keyboard_arrow_up</mat-icon>
</button>
</div>
示例 Typescript 组件代码:
@HostListener("window:scroll")
onWindowScroll() {
if (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop > 100) {
this.windowScrolled = true;
}
else if (this.windowScrolled && window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop < 10) {
this.windowScrolled = false;
}
}
scrollToTop() {
(function smoothscroll() {
var currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
if (currentScroll > 0) {
window.requestAnimationFrame(smoothscroll);
window.scrollTo(0, currentScroll - (currentScroll / 8));
}
})();
}
我的特定用例与原来的 post 不同,但相似度足以说明一些问题。如果您尝试使用 scrollToIndex
滚动到 CDK 的虚拟滚动视口的底部,它不起作用,而您最终来到这里,它可能会帮助您了解一些事情。
- 视口在
ngOnInit
中完成了异步初始化。 (参见 this code.) So if you try to interact with it before it is finished initializing, it silently fails. (This feature request 至少会在滚动空视口时提供错误。)
- 没有任何声明初始化完成。 (参见 this feature request。)
如果您在 cdk-virtual-scroll-viewport
的父组件的 ngAfterViewInit
中直接调用 scrollToIndex
,那将不起作用。在使用 scrollToIndex
之前,您必须等待一个滴答声。参见 this Stackblitz。重要位:
ngAfterViewInit(): void {
// does nothing
// this.viewPort.scrollTo({ bottom: 0, behavior: 'smooth' });
this.scrollToIndex$.next();
}
ngOnInit(): void {
this.subscriptions.add(
this.scrollToIndex$
.pipe(***-->delay(0)<--***)
.subscribe(() =>
this.viewPort.scrollTo({ bottom: 0, behavior: 'smooth' })
)
);
}
delay(0)
等待 Angular 生命周期的一个刻度,这似乎足以让视口自行完成。
目标
显示消息列表并在收到新消息时滚动到底部,即使我在顶部也是如此。即使有不同高度的元素,我也想完全滚动到底部。
问题
使用虚拟滚动,我必须设置 [itemSize]
属性,但对我来说它不是一个静态值:
- 当一条消息对于一行来说太长时,它会分成多行,因此它的高度会发生变化。
- 我有不同高度的不同类型的消息(例如系统消息)。
此外,我正在使用 ng-content
从父级插入一个按钮来加载以前的消息。我看到的是,当 _scrollToBottom
被调用时,它没有把我带到底部,而是把我带到了更高一点。我怀疑这是因为虚拟滚动中元素的高度不同。
我已经从 Angular 阅读了这个自动调整滚动策略问题:https://github.com/angular/components/issues/10113; 但我不确定这是否能解决我的问题。
任何关于我能做什么的想法都将受到欢迎。
测试
Codesandbox: https://codesandbox.io/s/angular-virtual-scroll-biwn6
- 加载消息后,向上滚动。
- 发送消息。 (加载新消息时,虚拟滚动不再滚动到底部,而是停在更高一点)
- 重复
有错误的视频:https://gofile.io/d/8NG9HD
解决方案
Gourav Garg 给出的解决方案有效。只需执行两次滚动功能即可。
我现在正在这样做:
private _scrollToBottom() {
setTimeout(() => {
this.virtualScrollViewport.scrollTo({
bottom: 0,
behavior: 'auto',
});
}, 0);
setTimeout(() => {
this.virtualScrollViewport.scrollTo({
bottom: 0,
behavior: 'auto',
});
}, 50);
}
我认为它不是很优雅,但工作正常。
如果你用的是cdk > 7就可以用
this.virtualScrollViewport.scrollToIndex(messages.length-1);
因为这将移至最后一项的顶部。您需要为该项目调用 scrollIntoView。
this.virtualScrollViewport.scrollToIndex(this.numbers.length - 1);
setTimeout(() => {
const items = document.getElementsByClassName("list-item");
items[items.length - 1].scrollIntoView();
}, 10);
<cdk-virtual-scroll-viewport #virtualScroll style="height: 500px" itemSize="90">
<ng-container *cdkVirtualFor="let n of numbers">
<li class="list-item"> {{n}} </li>
</ng-container>
</cdk-virtual-scroll-viewport>
我已经更新了你的sandbox
使用可以使用scrollIntoView()
滚动到virtualScrollViewport
this.virtualScrollViewport.scrollIntoView(false);
参考https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
有 cdk scroll 的替代方法。
这是一个通用版本的代码,用于滚动到给定的 HTML 元素。它可以用作 angular 中的服务功能,也可以用作 javascript 中的功能。
scroll(el: HTMLElement, behaviour: any = "smooth", block: any = "start", inline: any = "nearest") {
el.scrollIntoView({ behavior: behaviour, block: block, inline: inline })
}
样本HTML:
<div class="scroll-to-top" [ngClass]="{'show-scrollTop': windowScrolled}">
Back to top<button mat-button mat-icon-button (click)="scrollToTop()">
<mat-icon>keyboard_arrow_up</mat-icon>
</button>
</div>
示例 Typescript 组件代码:
@HostListener("window:scroll")
onWindowScroll() {
if (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop > 100) {
this.windowScrolled = true;
}
else if (this.windowScrolled && window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop < 10) {
this.windowScrolled = false;
}
}
scrollToTop() {
(function smoothscroll() {
var currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
if (currentScroll > 0) {
window.requestAnimationFrame(smoothscroll);
window.scrollTo(0, currentScroll - (currentScroll / 8));
}
})();
}
我的特定用例与原来的 post 不同,但相似度足以说明一些问题。如果您尝试使用 scrollToIndex
滚动到 CDK 的虚拟滚动视口的底部,它不起作用,而您最终来到这里,它可能会帮助您了解一些事情。
- 视口在
ngOnInit
中完成了异步初始化。 (参见 this code.) So if you try to interact with it before it is finished initializing, it silently fails. (This feature request 至少会在滚动空视口时提供错误。) - 没有任何声明初始化完成。 (参见 this feature request。)
如果您在 cdk-virtual-scroll-viewport
的父组件的 ngAfterViewInit
中直接调用 scrollToIndex
,那将不起作用。在使用 scrollToIndex
之前,您必须等待一个滴答声。参见 this Stackblitz。重要位:
ngAfterViewInit(): void {
// does nothing
// this.viewPort.scrollTo({ bottom: 0, behavior: 'smooth' });
this.scrollToIndex$.next();
}
ngOnInit(): void {
this.subscriptions.add(
this.scrollToIndex$
.pipe(***-->delay(0)<--***)
.subscribe(() =>
this.viewPort.scrollTo({ bottom: 0, behavior: 'smooth' })
)
);
}
delay(0)
等待 Angular 生命周期的一个刻度,这似乎足以让视口自行完成。