Angular 5 在每次路线点击时滚动到顶部

Posted

技术标签:

【中文标题】Angular 5 在每次路线点击时滚动到顶部【英文标题】:Angular 5 Scroll to top on every Route click 【发布时间】:2018-06-11 09:52:13 【问题描述】:

我正在使用 Angular 5。我有一个仪表板,其中有几个部分的内容很小,而几个部分的内容非常大,以至于在转到顶部时更改路由器时遇到问题。每次我需要滚动到顶部。

我该如何解决这个问题,以便当我更换路由器时,我的视图始终保持在顶部?

【问题讨论】:

Angular 2 Scroll to top on Route Change的可能重复 【参考方案1】:

有一些解决方案,请务必全部检查:)


选项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 中没有很好地实现平滑滚动,所以使用 this solution 来实现平滑滚动:

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 ) 

【讨论】:

window 对象上的滚动事件在 Angular 5 中不起作用。您猜猜为什么? @SahilBabbar,检查正文 css,溢出:隐藏?它的高度是多少? @Vega 没有。 body 的高度是默认的,内部没有硬编码,因为它是一个普通的 Angular 5 应用程序。此外,看看 Angular 文档,他们说 scroll 事件被 ngzones 列入黑名单。 试试 document.body.scrollTop = 0;或使用旧的 js document.querySelector('body').scrollTo(0,0) 等。如果这些不起作用,请提供 MCVE 延迟加载模块有什么办法吗?【参考方案2】:

这是一个仅在第一次访问每个组件时才会滚动到组件顶部的解决方案(以防您需要对每个组件执行不同的操作):

在每个组件中:

export class MyComponent implements OnInit 

firstLoad: boolean = true;

...

ngOnInit() 

  if(this.firstLoad) 
    window.scroll(0,0);
    this.firstLoad = false;
  
  ...

【讨论】:

【参考方案3】:

尽管@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;
      
    );
  

【讨论】:

感谢您提供高级代码和不错的解决方案。但有时@Vega 解决方案更好,因为它解决了动画和动态页面高度的许多问题。如果您有包含内容和简单路由动画的长页面,则您的解决方案很好。我在带有许多动画和动态块的页面上尝试它,它看起来不太好。我认为有时我们可以为我们的应用牺牲“后退位置”。但如果不是 - 你的解决方案是我对 Angular 看到的最好的解决方案。再次感谢您【参考方案4】:

我一直在寻找一个内置的解决方案来解决这个问题,就像在 AngularJS 中一样。但在那之前,这个解决方案对我有用,它很简单,并且保留了后退按钮的功能。

app.component.html

<router-outlet (deactivate)="onDeactivate()"></router-outlet>

app.component.ts

onDeactivate() 
  document.body.scrollTop = 0;
  // Alternatively, you can scroll to top by using this other call:
  // window.scrollTo(0, 0)

zurfyxoriginal post的回答

【讨论】:

【参考方案5】:

编辑:对于 Angular 6+,请使用 Nimesh Nishara Indimagedara 的回答提及:

RouterModule.forRoot(routes, 
    scrollPositionRestoration: 'enabled'
);

原答案:

如果全部失败,则在模板(或父模板)的顶部(或所需滚动到位置)创建一些空的 HTML 元素(例如:div):

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

【讨论】:

这个解决方案对我有用(在 Chrome 和 Edge 上测试)。接受的解决方案不适用于我的项目(Angular5) @RobvanMeeuwen,如果我的回答不起作用,那可能是您没有以相同的方式实现它。这个解决方案是直接操作不正确的 DOM 安全 @Vega,这就是我称它为 hack 的原因。您的解决方案是正确的。这里的一些人无法实现你的,所以我提供了后备黑客。他们应该根据他们目前使用的版本重构他们的代码。 所有这些解决方案都适合我。谢谢@GeoRover 对于 Angular 6+,请使用 Nimesh Nishara Indimagedara 的答案。【参考方案6】:

就我而言,我只是添加了

window.scroll(0,0);

ngOnInit() 中,它工作正常。

【讨论】:

【参考方案7】:

您只需要创建一个包含调整屏幕滚动的功能

例如

window.scroll(0,0) OR window.scrollTo() by passing appropriate parameter.

window.scrollTo(xpos, ypos) --> 预期参数。

【讨论】:

【参考方案8】:

现在 Angular 6.1 中有一个内置的解决方案,带有 scrollPositionRestoration 选项。

请在 Angular 2 Scroll to top on Route Change 上查看my answer。

【讨论】:

【参考方案9】:

试试这个:

app.component.ts

import Component, OnInit, OnDestroy from '@angular/core';
import Router, NavigationEnd from '@angular/router';
import filter from 'rxjs/operators';
import Subscription from 'rxjs';

@Component(
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss'],
)
export class AppComponent implements OnInit, OnDestroy 
    subscription: Subscription;

    constructor(private router: Router) 
    

    ngOnInit() 
        this.subscription = this.router.events.pipe(
            filter(event => event instanceof NavigationEnd)
        ).subscribe(() => window.scrollTo(0, 0));
    

    ngOnDestroy() 
        this.subscription.unsubscribe();
    

【讨论】:

【参考方案10】:

export class AppComponent 
  constructor(private router: Router) 
    router.events.subscribe((val) => 
      if (val instanceof NavigationEnd) 
        window.scrollTo(0, 0);
      
    );
  

【讨论】:

【参考方案11】:

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

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

【讨论】:

请注意,至少在 2019 年 11 月 6 日使用 Angular 8 时,scrollPositionRestoration 属性不适用于动态页面内容(即,页面内容是异步加载的):请参阅此角度错误报告:github.com/angular/angular/issues/24547【参考方案12】:

组件:订阅所有路由事件,而不是在模板中创建操作并在 NavigationEnd b/c 上滚动,否则您将在导航错误或路线阻塞等情况下触发此功能...知道如果成功导航到路线,则安抚滚动。否则,什么也不做。

@Component(
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
)
export class AppComponent implements OnInit, OnDestroy 

  router$: Subscription;

  constructor(private router: Router) 

  ngOnInit() 
    this.router$ = this.router.events.subscribe(next => this.onRouteUpdated(next));
  

  ngOnDestroy() 
    if (this.router$ != null) 
      this.router$.unsubscribe();
    
  

  private onRouteUpdated(event: any): void 
    if (event instanceof NavigationEnd) 
      this.smoothScrollTop();
    
  

  private smoothScrollTop(): void 
    const scrollToTop = window.setInterval(() => 
      const pos: number = window.pageYOffset;
      if (pos > 0) 
          window.scrollTo(0, pos - 20); // how far to scroll on each step
       else 
          window.clearInterval(scrollToTop);
      
    , 16);
  



HTML

<router-outlet></router-outlet>

【讨论】:

【参考方案13】:

试试这个

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

此代码支持 angular 6

【讨论】:

【参考方案14】:

来自 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' in

示例:

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

【讨论】:

奇怪的是,每个解决方案在某些情况下都有效,而在其他情况下则失败。角滚动到顶部存在严重缺陷。 我能够将 ViewportScroller 注入到我的组件中。它醒了。【参考方案15】:

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

注意:预期的行为是,当您导航回页面时,它应该保持向下滚动到单击链接时的相同位置,但在到达每个页面时滚动到顶部。

【讨论】:

【参考方案16】:

如果您使用 mat-sidenav 为路由器插座提供一个 ID(如果您有父路由器插座和子路由器插座)并在其中使用激活功能 &lt;router-outlet id="main-content" (activate)="onActivate($event)"&gt; 并使用此“mat-sidenav-content”查询选择器滚动顶部 onActivate(event) document.querySelector("mat-sidenav-content").scrollTo(0, 0);

【讨论】:

即使不使用id 也能很好地工作(我的应用程序上有一个router-outlet)。我还以更“角度”的方式做到了:@ViewChild(MatSidenavContainer) sidenavContainer: MatSidenavContainer; onActivate() this.sidenavContainer.scrollable.scrollTo( left: 0, top: 0 ); 【参考方案17】:

只需添加

window.scrollTo( top: 0);

到 ngOnInit()

【讨论】:

window.scroll(0,0)【参考方案18】:

对于那些正在寻找滚动功能的人来说,只需添加该功能并在需要时调用

scrollbarTop()

  window.scroll(0,0);

【讨论】:

【参考方案19】:

由于某种原因,上述方法都不适合我:/,所以我在 app.component.html 的顶部元素中添加了一个元素 ref,在 router-outlet 中添加了 (activate)=onNavigate($event)

<!--app.component.html-->
<div #topScrollAnchor></div>
<app-navbar></app-navbar>
<router-outlet (activate)="onNavigate($event)"></router-outlet>

然后我将子元素添加到 app.component.ts 文件中,类型为 ElementRef,并在激活路由器插座时滚动到它。

export class AppComponent  
  @ViewChild('topScrollAnchor') topScroll: ElementRef;

  onNavigate(event): any 
    this.topScroll.nativeElement.scrollIntoView( behavior: 'smooth' );
  

这是stackblitz中的代码

【讨论】:

这也是唯一一个为我工作的人。我正在使用 ion-split-pane,window.scroll 似乎不起作用。【参考方案20】:

对我有用的解决方案:

document.getElementsByClassName('layout-content')[0].scrollTo(0, 0);

它适用于角度 8、9 和 10。

【讨论】:

【参考方案21】:

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

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

我正在使用 Angular 11.1.4,它对我有用

【讨论】:

以上是关于Angular 5 在每次路线点击时滚动到顶部的主要内容,如果未能解决你的问题,请参考以下文章

在路线更改和新组件加载不起作用后以角度滚动到顶部

更改路线不会在新页面中滚动到顶部

在 angular2 的新页面中,路线更改视图不会滚动到顶部

Angular2 Meteor,实现无限滚动的问题(滚动重置到顶部)

防止在Angular中的单个router.navigate调用上滚动到顶部

如何使用反应路由器 dom v6 在路由更改时滚动到顶部?