每次路由点击时,将Angular 5滚动到顶部

181

我正在使用 Angular 5。我的仪表板中有几个小内容部分和几个非常大的内容部分,当我在向上导航时会遇到一个问题,需要滚动才能回到顶部。

如何解决这个问题,使得当我切换路由时,我的视图总是停留在顶部?


24个回答

334

以下是一些解决方案,请确保检查它们全部:)


选项1:

路由出口将在任何时候实例化新组件时发出activate事件,因此我们可以使用(activate)来滚动(例如)到顶部:

app.component.html

<router-outlet (activate)="onActivate($event)"></router-outlet>

app.component.ts

onActivate(event) {
   // window.scroll(0,0);

   window.scroll({ 
           top: 0, 
           left: 0, 
           behavior: 'smooth' 
    });

    //or document.body.scrollTop = 0;
    //or document.querySelector('body').scrollTo(0,0)
    ...
}

由于Safari中平滑滚动的实现不太好,因此可以使用例如此解决方案来实现平滑滚动:

onActivate(event) {
    let scrollToTop = window.setInterval(() => {
        let pos = window.pageYOffset;
        if (pos > 0) {
            window.scrollTo(0, pos - 20); // how far to scroll on each step
        } else {
            window.clearInterval(scrollToTop);
        }
    }, 16);
}
如果你想要进行选择,比如不希望每个组件都触发滚动,你可以在if语句中进行检查,例如下面这样:
onActivate(e) {
    if (e.constructor.name)==="login"{ // for example
            window.scroll(0,0);
    }
}

选项2:

自 Angular6.1 以来,我们还可以在急切加载的模块上使用 { scrollPositionRestoration: 'enabled' },并且它将应用于所有路由:

RouterModule.forRoot(appRoutes, { scrollPositionRestoration: 'enabled' })

它已经可以进行平滑滚动了。但是这样做对于每次路由导航都要进行滚动来说有些不方便。


选项3:

另一种解决方案是在路由动画中进行顶部滚动。在想要滚动到顶部的每个转换中添加此内容:

query(':enter, :leave', style({ position: 'fixed' }), { optional: true }) 

2
window对象上的滚动事件在Angular 5中无法工作。有什么猜测原因吗? - Sahil Babbar
谢谢你的回答,解决了我的问题。我还将代码放在了ngOnInit()方法中,以使其适用于选项卡导航。 - rzv
2
有没有懒加载模块的方法? - Pankaj Prakash
谢谢!这解决了我在页面上执行某些操作后滚动到顶部的问题,与导航不完全相关。 - Bruno T. Abrahão
我想知道为什么{ scrollPositionRestoration: 'enabled' }还不是默认设置。虽然在我的懒加载模块中它起作用了。@PankajPrakash - BrunoElo
显示剩余6条评论

71

如果您在Angular 6中遇到此问题,可以通过向app-routing.module.ts的RouterModule添加参数scrollPositionRestoration: 'enabled'来解决:

@NgModule({
  imports: [RouterModule.forRoot(routes,{
    scrollPositionRestoration: 'enabled'
  })],
  exports: [RouterModule]
})

6
请注意,截至2019年11月6日,至少在使用Angular 8时,scrollPositionRestoration属性不适用于动态页面内容(即异步加载页面内容的情况):请参见此Angular错误报告:https://github.com/angular/angular/issues/24547 - dbeachy1

43

编辑:对于Angular 6+,请使用Nimesh Nishara Indimagedara提供的答案,它提到:

RouterModule.forRoot(routes, {
    scrollPositionRestoration: 'enabled'
});
如果一切都失败了,那么在模板(或父模板)的顶部(或所需滚动到的位置)创建一些空的HTML元素(例如:div),并将其id设置为“top”即可。
<div id="top"></div>

在组件中:

  ngAfterViewInit() {
    // Hack: Scrolls to top of Page after page view initialized
    let top = document.getElementById('top');
    if (top !== null) {
      top.scrollIntoView();
      top = null;
    }
  }

2
这个解决方案对我有效(在Chrome和Edge上测试过)。被接受的解决方案在我的项目(Angular5)中没有起作用。 - Rob van Meeuwen
@RobvanMeeuwen,如果我的答案不起作用,那么很可能是你没有按照相同的方式实现它。这个解决方案直接操作DOM,这也不正确或安全。 - Vega
@Vega,这就是我称之为hack的原因。你的解决方案是正确的。这里有些人无法实现你的方案,所以我提供了备用的hack。他们应该根据当前版本重构他们的代码。 - GeoRover
从所有的解决方案中,这个对我有效。谢谢@GeoRover。 - Gangani Roshan
对于Angular 6+,请使用Nimesh Nishara Indimagedara的答案。 - GeoRover

14

12

从 Angular 版本 6+ 开始,无需使用 window.scroll(0,0)

对于 Angular 版本6+来自 @docs
表示配置路由的选项。

interface ExtraOptions {
  enableTracing?: boolean
  useHash?: boolean
  initialNavigation?: InitialNavigation
  errorHandler?: ErrorHandler
  preloadingStrategy?: any
  onSameUrlNavigation?: 'reload' | 'ignore'
  scrollPositionRestoration?: 'disabled' | 'enabled' | 'top'
  anchorScrolling?: 'disabled' | 'enabled'
  scrollOffset?: [number, number] | (() => [number, number])
  paramsInheritanceStrategy?: 'emptyOnly' | 'always'
  malformedUriErrorHandler?: (error: URIError, urlSerializer: UrlSerializer, url: string) => UrlTree
  urlUpdateStrategy?: 'deferred' | 'eager'
  relativeLinkResolution?: 'legacy' | 'corrected'
}

在某些情况下,可以使用 scrollPositionRestoration?: 'disabled' | 'enabled' | 'top'

示例:

RouterModule.forRoot(routes, {
    scrollPositionRestoration: 'enabled'|'top' 
});

如果需要手动控制滚动,无需使用window.scroll(0,0),而是从 Angular V6 通用包中引入了ViewPortScoller

abstract class ViewportScroller {
  static ngInjectableDef: defineInjectable({ providedIn: 'root', factory: () => new BrowserViewportScroller(inject(DOCUMENT), window) })
  abstract setOffset(offset: [number, number] | (() => [number, number])): void
  abstract getScrollPosition(): [number, number]
  abstract scrollToPosition(position: [number, number]): void
  abstract scrollToAnchor(anchor: string): void
  abstract setHistoryScrollRestoration(scrollRestoration: 'auto' | 'manual'): void
}

使用方法非常简单 例子:

import { Router } from '@angular/router';
import {  ViewportScroller } from '@angular/common'; //import
export class RouteService {

  private applicationInitialRoutes: Routes;
  constructor(
    private router: Router;
    private viewPortScroller: ViewportScroller//inject
  )
  {
   this.router.events.pipe(
            filter(event => event instanceof NavigationEnd))
            .subscribe(() => this.viewPortScroller.scrollToPosition([0, 0]));
}

2
奇怪的是,每个解决方案在某些情况下都有效,但在其他情况下则失败。Angular滚动到顶部存在严重缺陷。 - Sajib Acharya
1
我成功将ViewportScroller注入到了我的组件中,并且它工作正常。 - Code Name Jack
2
使用viewPortScroller.scrollToPosition([0, 0]并不总是滚动到顶部。将参数更改为[0,-50],每次都可以正常工作。在我的情况下,在app.component.html中的<router-outlet>上方有一个大约50像素高的页面标题组件。理论上,scrollToPosition相对于<router-outlet>进行滚动。 - Paul Evans

7

虽然 @Vega 提供了直接回答您问题的方法,但存在问题。它会破坏浏览器的后退/前进按钮。如果用户点击浏览器的后退或前进按钮,他们就会失去自己的位置并被滚动到顶部。如果用户不得不向下滚动才能找到一个链接,然后决定单击后退,只发现滚动条已经被重置到顶部,这可能会给用户带来一些困扰。

以下是我解决这个问题的方法。

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;
      }
    });
  }
}

2
感谢您提供的高级代码和优秀解决方案。但有时候@Vega的解决方案更好,因为它可以解决许多动画和动态页面高度的问题。如果您的页面内容很长且路由动画简单,则您的解决方案很好。我在具有许多动画和动态块的页面上尝试了它,效果不是很好。我认为有时我们可以为我们的应用程序牺牲“后退位置”。但如果没有其他选择,您的解决方案是我见过的Angular最好的解决方案。再次感谢您。 - Denis Savenko

6

如果您正在使用mat-sidenav,请给路由器出口(如果您有父级和子级路由器出口)分配一个ID,并在其中使用activate函数。 <router-outlet id="main-content" (activate)="onActivate($event)"> 并使用此“mat-sidenav-content”查询选择器滚动到顶部。 onActivate(event) { document.querySelector("mat-sidenav-content").scrollTo(0, 0); }


即使没有使用 id(我在应用程序上只有一个 router-outlet),也能很好地工作。我还以更“Angular”的方式完成了它:@ViewChild(MatSidenavContainer) sidenavContainer: MatSidenavContainer; onActivate() { this.sidenavContainer.scrollable.scrollTo({ left: 0, top: 0 }); } - GCSDC
谢谢,你是唯一一个提到这个解决方案的人。为什么这个问题没有被报告给 Angular 团队,或者至少为什么在他们的网站上没有提到呢? - undefined

6

只需在app.module.ts文件中添加此行:

RouterModule.forRoot(routes, {
    scrollPositionRestoration: 'enabled' //scroll to the top
})

我正在使用Angular 11.1.4版本,它对我很有效。


6
在我的情况下,我只是添加了。
window.scroll(0,0);

ngOnInit()中,它可以正常工作。


6

Angular 6.1 及以上版本:

你可以使用 Angular 6.1+ 中提供的 内置解决方案,并使用选项 scrollPositionRestoration: 'enabled' 来达到相同的效果。

@NgModule({
  imports: [RouterModule.forRoot(routes,{
    scrollPositionRestoration: 'enabled'
  })],
  exports: [RouterModule]
})

Angular 6.0及以下版本:

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);
            }
        });
    }
}

注意:预期的行为是当您返回页面时,它应该仍停留在您点击链接时的同一位置,但到达每个页面时都要滚动到顶部。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接