Angular 2在Route Change上滚动到顶部

Nav*_*med 246 typescript angular2-directives angular2-template angular2-routing angular

在我的Angular 2应用程序中,当我向下滚动页面并单击页面底部的链接时,它确实会更改路径并将我带到下一页但它不会滚动到页面顶部.结果,如果第一页很长而第二页的内容很少,则给人的印象是第二页缺少内容.由于只有当用户滚动到页面顶部时内容才可见.

我可以在组件的ngInit中将窗口滚动到页面顶部但是,有没有更好的解决方案可以自动处理我的应用程序中的所有路径?

Gui*_*les 343

您可以在主要组件上注册路线更改侦听器,并在路线更改时滚动到顶部.

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)
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

  • `window.scrollTo(0,0)`比`document.body.scrollTop = 0;`更简洁,更可读的IMO. (11认同)
  • 有没有人注意到,即使实现了这个,问题仍然存在于Iphone的safari浏览器中.有什么想法吗? (9认同)
  • 这工作!! 虽然我添加了`$("body").animate({scrollTop:0},1000);`而不是`window.scrollTo(0,0)`来动画平滑滚动到顶部 (6认同)
  • 这对我有用,但它打破了默认的"后退"按钮行为.回去应该记住上一个滚动位置. (3认同)

Fer*_*ria 289

Angular 6.1及更高版本:

Angular 6.1(发布于2018-07-25)通过名为"路由器滚动位置恢复"的功能增加了内置支持来处理此问题.如官方Angular博客中所述,您只需在路由器配置中启用此功能,如下所示:

RouterModule.forRoot(routes, {scrollPositionRestoration: 'enabled'})
Run Code Online (Sandbox Code Playgroud)

此外,该博客指出"预计这将成为未来主要版本的默认版本",因此很可能从Angular 7开始,您不需要在代码中做任何事情,这只会起作用开箱即用.

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);
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 您可以这样做,它将使代码与其他非浏览器平台更广泛地兼容.有关实施详细信息,请参阅/sf/ask/2392405501/. (3认同)
  • 这应该是接受的答案,因此它不会突破后退行为. (3认同)
  • 如果在现代浏览器中单击并按住后退/前进按钮,则会出现一个菜单,可让您导航到上一个/下一个之外的其他位置.如果你这样做,这个解决方案会中断 这是大多数人的优势,但值得一提. (3认同)
  • 这应该直接在app组件中,或者在其中使用的单个组件中(因此由整个app共享).例如,我将它包含在顶部导航栏组件中.您不应该包含在所有组件中. (2认同)

Abd*_*fay 56

从Angular 6.1开始,您现在可以避免麻烦并将extraOptionsRouterModule.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 { }
Run Code Online (Sandbox Code Playgroud)

Angular Official Docs

  • 即使上面的答案更具描述性,我还是喜欢这个答案,告诉了我确切的去向 (2认同)

mtp*_*ltz 30

您可以通过利用可观察的filter方法更简洁地编写这个:

this.router.events.filter(event => event instanceof NavigationEnd).subscribe(() => {
      this.window.scrollTo(0, 0);
});
Run Code Online (Sandbox Code Playgroud)

如果您在使用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);
});
Run Code Online (Sandbox Code Playgroud)

此外,Angular CDK v6.x 现在有一个滚动包,可能有助于处理滚动.

  • 大!对于我工作的人-`document.querySelector('。mat-sidenav-content .content-div')。scrollTop = 0;` (2认同)

Rap*_*tor 16

如果您有服务器端呈现,则应注意不要windows在服务器上运行代码,其中该变量不存在.这会导致代码破坏.

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();
  }
}
Run Code Online (Sandbox Code Playgroud)

isPlatformBrowser是一个函数,用于检查应用程序呈现的当前平台是否是浏览器.我们给它注射platformId.

它也可以检查变量是否存在windows,是安全的,如下所示:

if (typeof window != 'undefined')
Run Code Online (Sandbox Code Playgroud)


小智 12

只需点击操作即可轻松完成

在你的主要组件html中引用#scrollContainer

<div class="main-container" #scrollContainer>
    <router-outlet (activate)="onActivate($event, scrollContainer)"></router-outlet>
</div>
Run Code Online (Sandbox Code Playgroud)

在主要组件.ts

onActivate(e, scrollContainer) {
    scrollContainer.scrollTop = 0;
}
Run Code Online (Sandbox Code Playgroud)


zur*_*fyx 11

最好的答案在于Angular GitHub讨论(更改路线不会在新页面中滚动到顶部).

也许你只想在根路由器更改中转到顶部(不是在子节点中,因为你可以在一个tabset中加载延迟加载的路由)

app.component.html

<router-outlet (deactivate)="onDeactivate()"></router-outlet>
Run Code Online (Sandbox Code Playgroud)

app.component.ts

onDeactivate() {
  document.body.scrollTop = 0;
  // Alternatively, you can scroll to top by using this other call:
  // window.scrollTo(0, 0)
}
Run Code Online (Sandbox Code Playgroud)

JoniJnm的全部学分(原帖)


sti*_*nux 8

您可以将AfterViewInit生命周期钩子添加到组件中.

ngAfterViewInit() {
   window.scrollTo(0, 0);
}
Run Code Online (Sandbox Code Playgroud)


小智 6

从Angular 6.1开始,路由器提供了一个名为的配置选项scrollPositionRestoration,旨在满足这种情况。

imports: [
  RouterModule.forRoot(routes, {
    scrollPositionRestoration: 'enabled'
  }),
  ...
]
Run Code Online (Sandbox Code Playgroud)


Ife*_*yan 6

除了@Guilherme Meireles提供的完美答案(如下所示)之外,您还可以通过添加平滑滚动来调整您的实现,如下所示

 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)
            });
        }
    }
Run Code Online (Sandbox Code Playgroud)

然后添加下面的代码段

 html {
      scroll-behavior: smooth;
    }
Run Code Online (Sandbox Code Playgroud)

到你的styles.css


Pra*_*R.V 6

Angular最近引入了一项新功能,内部的角路由模块进行如下更改

@NgModule({
  imports: [RouterModule.forRoot(routes,{
    scrollPositionRestoration: 'top'
  })],
Run Code Online (Sandbox Code Playgroud)


Sal*_*808 5

这是我想出的解决方案。我将 LocationStrategy 与 Router 事件配对。使用 LocationStrategy 设置一个布尔值以了解用户当前何时遍历浏览器历史记录。这样,我就不必存储一堆 URL 和 y-scroll 数据(无论如何都不能很好地工作,因为每个数据都是基于 URL 替换的)。这也解决了当用户决定按住浏览器上的后退或前进按钮并后退或前进多个页面而不是一个页面时的边缘情况。

PS 我只在最新版本的 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;
      }
    });
  }
}
Run Code Online (Sandbox Code Playgroud)