在我的Angular 2应用中,当我向下滚动页面并单击页面底部的链接时,它确实会改变路由并将我带到下一页,但它不会滚动到页面顶部。因此,如果第一页很长,第二页内容很少,就会给人一种第二页缺乏内容的印象。因为只有当用户滚动到页面顶部时,内容才可见。
我可以滚动窗口到ngInit组件的页面顶部,但是,有没有更好的解决方案,可以自动处理我的应用程序中的所有路由?
在我的Angular 2应用中,当我向下滚动页面并单击页面底部的链接时,它确实会改变路由并将我带到下一页,但它不会滚动到页面顶部。因此,如果第一页很长,第二页内容很少,就会给人一种第二页缺乏内容的印象。因为只有当用户滚动到页面顶部时,内容才可见。
我可以滚动窗口到ngInit组件的页面顶部,但是,有没有更好的解决方案,可以自动处理我的应用程序中的所有路由?
当前回答
这个解决方案基于@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);
}
}
});
}
}
其他回答
@Fernando埃切维里亚 太棒了!但是这段代码不能在哈希路由器或惰性路由器中工作。因为它们不会触发位置更改。 可以试试这个:
private lastRouteUrl: string[] = [] ngOnInit(): void { This.router.events.subscribe ((ev) => { const len = this.lastRouteUrl.length if (ev instanceof NavigationEnd) { this.lastRouteUrl.push (ev.url) If (len > 1 && ev。Url === this。lastRouteUrl[len - 2]) { 返回 } 窗口。scrollTo (0, 0) } }) }
如果您使用服务器端呈现,则应该注意不要在服务器上使用窗口运行代码,因为服务器上不存在该变量。这将导致代码被破坏。
export class AppComponent implements OnInit {
routerSubscription: Subscription;
constructor(private router: Router,
@Inject(PLATFORM_ID) private platformId: any) {}
ngOnInit() {
if (isPlatformBrowser(this.platformId)) {
this.routerSubscription = this.router.events
.filter(event => event instanceof NavigationEnd)
.subscribe(event => {
window.scrollTo(0, 0);
});
}
}
ngOnDestroy() {
this.routerSubscription.unsubscribe();
}
}
isPlatformBrowser是一个函数,用于检查当前应用程序所呈现的平台是否是浏览器。我们给它注入platformId。
它也可以检查变量窗口的存在,为了安全起见,像这样:
if (typeof window != 'undefined')
从Angular 6.1开始,你现在可以避免这种麻烦,将extraOptions作为第二个参数传递给RouterModule.forRoot(),并可以指定scrollPositionRestoration: enabled来告诉Angular当路由发生变化时滚动到顶部。
默认情况下,你会在app-routing.module.ts中找到这个:
const routes: Routes = [
{
path: '...'
component: ...
},
...
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled', // Add options right here
})
],
exports: [RouterModule]
})
export class AppRoutingModule { }
Angular官方文档
使用路由器本身会导致你无法完全克服的问题,以保持一致的浏览器体验。在我看来,最好的方法是使用一个自定义指令,让这重置滚动点击。这样做的好处是,如果你在相同的url,你点击,页面将滚动回顶部以及。这与一般的网站是一致的。基本指令可以是这样的:
import {Directive, HostListener} from '@angular/core';
@Directive({
selector: '[linkToTop]'
})
export class LinkToTopDirective {
@HostListener('click')
onClick(): void {
window.scrollTo(0, 0);
}
}
具有以下用途:
<a routerLink="/" linkToTop></a>
这对于大多数用例来说已经足够了,但是我可以想象一些问题 由此产生:
由于window的使用,不能在通用上工作 对更改检测的速度影响很小,因为它是由每次单击触发的 没有办法禁用这个指令
克服这些问题其实很容易:
@Directive({
selector: '[linkToTop]'
})
export class LinkToTopDirective implements OnInit, OnDestroy {
@Input()
set linkToTop(active: string | boolean) {
this.active = typeof active === 'string' ? active.length === 0 : active;
}
private active: boolean = true;
private onClick: EventListener = (event: MouseEvent) => {
if (this.active) {
window.scrollTo(0, 0);
}
};
constructor(@Inject(PLATFORM_ID) private readonly platformId: Object,
private readonly elementRef: ElementRef,
private readonly ngZone: NgZone
) {}
ngOnDestroy(): void {
if (isPlatformBrowser(this.platformId)) {
this.elementRef.nativeElement.removeEventListener('click', this.onClick, false);
}
}
ngOnInit(): void {
if (isPlatformBrowser(this.platformId)) {
this.ngZone.runOutsideAngular(() =>
this.elementRef.nativeElement.addEventListener('click', this.onClick, false)
);
}
}
}
这考虑了大多数用例,使用方法与基本用例相同,优点是启用/禁用它:
<a routerLink="/" linkToTop></a> <!-- always active -->
<a routerLink="/" [linkToTop]="isActive"> <!-- active when `isActive` is true -->
如果你不想被广告吸引,就不要看广告
Another improvement could be made to check whether or not the browser supports passive events. This will complicate the code a bit more, and is a bit obscure if you want to implement all these in your custom directives/templates. That's why I wrote a little library which you can use to address these problems. To have the same functionality as above, and with the added passive event, you can change your directive to this, if you use the ng-event-options library. The logic is inside the click.pnb listener:
@Directive({
selector: '[linkToTop]'
})
export class LinkToTopDirective {
@Input()
set linkToTop(active: string|boolean) {
this.active = typeof active === 'string' ? active.length === 0 : active;
}
private active: boolean = true;
@HostListener('click.pnb')
onClick(): void {
if (this.active) {
window.scrollTo(0, 0);
}
}
}
您可以在主组件上注册路由更改侦听器,并在路由更改时滚动到顶部。
import { Component, OnInit } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
@Component({
selector: 'my-app',
template: '<ng-content></ng-content>',
})
export class MyAppComponent implements OnInit {
constructor(private router: Router) { }
ngOnInit() {
this.router.events.subscribe((evt) => {
if (!(evt instanceof NavigationEnd)) {
return;
}
window.scrollTo(0, 0)
});
}
}