在我的Angular 2应用中,当我向下滚动页面并单击页面底部的链接时,它确实会改变路由并将我带到下一页,但它不会滚动到页面顶部。因此,如果第一页很长,第二页内容很少,就会给人一种第二页缺乏内容的印象。因为只有当用户滚动到页面顶部时,内容才可见。
我可以滚动窗口到ngInit组件的页面顶部,但是,有没有更好的解决方案,可以自动处理我的应用程序中的所有路由?
在我的Angular 2应用中,当我向下滚动页面并单击页面底部的链接时,它确实会改变路由并将我带到下一页,但它不会滚动到页面顶部。因此,如果第一页很长,第二页内容很少,就会给人一种第二页缺乏内容的印象。因为只有当用户滚动到页面顶部时,内容才可见。
我可以滚动窗口到ngInit组件的页面顶部,但是,有没有更好的解决方案,可以自动处理我的应用程序中的所有路由?
当前回答
从Angular 6.1开始,路由器提供了一个名为scrollPositionRestoration的配置选项,这是为了满足这种情况而设计的。
imports: [
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled'
}),
...
]
其他回答
你可以利用observable filter方法更简洁地写这个:
this.router.events.filter(event => event instanceof NavigationEnd).subscribe(() => {
this.window.scrollTo(0, 0);
});
如果你在使用Angular Material 2 sidenav时遇到滚动到顶部的问题,这将有所帮助。窗口或文档主体不会有滚动条,因此您需要获得sidenav内容容器并滚动该元素,否则尝试将滚动窗口作为默认值。
this.router.events.filter(event => event instanceof NavigationEnd)
.subscribe(() => {
const contentContainer = document.querySelector('.mat-sidenav-content') || this.window;
contentContainer.scrollTo(0, 0);
});
还有,Angular CDK v6。X现在有一个滚动包,可以帮助处理滚动。
这个解决方案基于@FernandoEcheverria和@GuilhermeMeireles的解决方案,但它更简洁,并且可以使用Angular路由器提供的popstate机制。这允许存储和恢复多个连续导航的滚动级别。
我们将每个导航状态的滚动位置存储在一个地图scrollLevels中。一旦有了popstate事件,即将被恢复的状态ID就会由Angular路由器提供:event. restoredstate . navigationid。然后,这将用于从scrollLevels获取该状态的最后一个滚动级别。
如果路由没有存储滚动级别,它将滚动到顶部,正如您所期望的那样。
import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
@Component({
selector: 'my-app',
template: '<ng-content></ng-content>',
})
export class AppComponent implements OnInit {
constructor(private router: Router) { }
ngOnInit() {
const scrollLevels: { [navigationId: number]: number } = {};
let lastId = 0;
let restoredId: number;
this.router.events.subscribe((event: Event) => {
if (event instanceof NavigationStart) {
scrollLevels[lastId] = window.scrollY;
lastId = event.id;
restoredId = event.restoredState ? event.restoredState.navigationId : undefined;
}
if (event instanceof NavigationEnd) {
if (restoredId) {
// Optional: Wrap a timeout around the next line to wait for
// the component to finish loading
window.scrollTo(0, scrollLevels[restoredId] || 0);
} else {
window.scrollTo(0, 0);
}
}
});
}
}
Here's a solution that I've come up with. I paired up the LocationStrategy with the Router events. Using the LocationStrategy to set a boolean to know when a user's currently traversing through the browser history. This way, I don't have to store a bunch of URL and y-scroll data (which doesn't work well anyway, since each data is replaced based on URL). This also solves the edge case when a user decides to hold the back or forward button on a browser and goes back or forward multiple pages rather than just one.
附注:我只测试了最新版本的IE、Chrome、FireFox、Safari和Opera(截至本文)。
希望这能有所帮助。
export class AppComponent implements OnInit {
isPopState = false;
constructor(private router: Router, private locStrat: LocationStrategy) { }
ngOnInit(): void {
this.locStrat.onPopState(() => {
this.isPopState = true;
});
this.router.events.subscribe(event => {
// Scroll to top if accessing a page, not via browser history stack
if (event instanceof NavigationEnd && !this.isPopState) {
window.scrollTo(0, 0);
this.isPopState = false;
}
// Ensures that isPopState is reset
if (event instanceof NavigationEnd) {
this.isPopState = false;
}
});
}
}
Angular 6.1及更高版本:
Angular 6.1(发布于2018-07-25)通过一个名为“路由器滚动位置恢复”的特性,添加了内置支持来处理这个问题。正如Angular官方博客中所描述的,你只需要在路由器配置中启用它,如下所示:
RouterModule.forRoot(routes, {scrollPositionRestoration: 'enabled'})
此外,该博客称“预计这将成为未来主要版本的默认设置”。到目前为止,这种情况还没有发生(从Angular 11.0开始),但最终你将不需要在代码中做任何事情,它将会正确地开箱工作。
你可以在官方文档中看到更多关于这个特性以及如何定制这个行为的细节。
Angular 6.0及更早版本:
虽然@GuilhermeMeireles的出色回答解决了最初的问题,但它引入了一个新问题,打破了向后或向前导航(使用浏览器按钮或通过代码中的位置)时预期的正常行为。预期的行为是,当您导航回页面时,它应该保持向下滚动到与单击链接时相同的位置,但在到达每个页面时滚动到顶部显然打破了这一预期。
下面的代码扩展了检测这种导航的逻辑,方法是订阅Location的PopStateEvent序列,如果新到达的页面是这种事件的结果,则跳过滚动到顶部的逻辑。
如果你返回的页面足够长,足以覆盖整个视口,滚动位置将自动恢复,但正如@JordanNelson正确指出的那样,如果页面较短,你需要跟踪原始的y轴滚动位置,并在返回页面时显式地恢复它。代码的更新版本也涵盖了这种情况,总是显式地恢复滚动位置。
import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
import { Location, PopStateEvent } from "@angular/common";
@Component({
selector: 'my-app',
template: '<ng-content></ng-content>',
})
export class MyAppComponent implements OnInit {
private lastPoppedUrl: string;
private yScrollStack: number[] = [];
constructor(private router: Router, private location: Location) { }
ngOnInit() {
this.location.subscribe((ev:PopStateEvent) => {
this.lastPoppedUrl = ev.url;
});
this.router.events.subscribe((ev:any) => {
if (ev instanceof NavigationStart) {
if (ev.url != this.lastPoppedUrl)
this.yScrollStack.push(window.scrollY);
} else if (ev instanceof NavigationEnd) {
if (ev.url == this.lastPoppedUrl) {
this.lastPoppedUrl = undefined;
window.scrollTo(0, this.yScrollStack.pop());
} else
window.scrollTo(0, 0);
}
});
}
}
从Angular 6.1开始,路由器提供了一个名为scrollPositionRestoration的配置选项,这是为了满足这种情况而设计的。
imports: [
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled'
}),
...
]