Angulaire 2 faire Défiler vers le haut sur la Route du Changement

dans mon application Angular 2 Quand je fais défiler une page et que je clique sur le lien au bas de la page, cela change la route et m'amène à la page suivante, mais ça ne fait pas défiler jusqu'au haut de la page. En conséquence, si la première page est longue et 2ème page a peu de contenu, il donne une impression que la 2ème page manque de contenu. Puisque le contenu est visible uniquement si l'utilisateur fait défiler vers le haut de la page.

je peux faire défiler la fenêtre vers le haut de la page dans ngInit du composant mais, est-il une meilleure solution qui permet de gérer automatiquement toutes les routes dans mon application?

133
demandé sur Rohit Sharma 2016-09-20 21:13:46

18 réponses

vous pouvez enregistrer un changement de route sur votre composante principale et faire défiler vers le haut sur les changements de route.

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)
        });
    }
}
235
répondu Guilherme Meireles 2017-03-03 13:51:16

Angulaire 6.1 et plus tard :

Angular 6.1 (sorti le 2018-07-25) a ajouté un support intégré pour traiter ce problème, par le biais d'une fonctionnalité appelée "restauration de la Position du rouleau de routeur". Comme décrit dans l'officiel blog Angular , vous avez juste besoin d'activer cela dans la configuration du routeur comme ceci:

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

en outre, le blog déclare "Il est prévu que cela deviendra le défaut dans un future version majeure", il est donc probable que Angulaires 7, vous n'aurez pas besoin de faire quoi que ce soit dans votre code, et ce, tout fonctionne correctement hors de la boîte.

Angulaire 6.0 et versions antérieures :

alors que L'excellente réponse de @GuilhermeMeireles corrige le problème original, elle en introduit un nouveau, en brisant le comportement normal que vous attendez lorsque vous naviguez en arrière ou en avant (avec des boutons de navigateur ou via L'emplacement dans le code). Le le comportement attendu est que lorsque vous retournez à la page, il doit rester défiler vers le bas à l'endroit où il était lorsque vous avez cliqué sur le lien, mais défiler vers le haut en arrivant à chaque page brise évidemment cette attente.

le code ci-dessous développe la logique pour détecter ce type de navigation en souscrivant à la séquence Popstaeevent de L'emplacement et en sautant la logique de défilement vers le haut si la page nouvellement arrivée est le résultat d'un tel événement.

si la page d'où vous revenez est assez longue pour couvrir l'ensemble du viewport, la position de défilement est restaurée automatiquement, mais comme @JordanNelson l'a correctement souligné, si la page est plus courte, vous devez garder une trace de la position de défilement en y originale et restaurée explicitement lorsque vous retournez à la page. La version mise à jour du code couvre également ce cas, en rétablissant toujours explicitement la position de défilement.

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);
            }
        });
    }
}
75
répondu Fernando Echeverria 2018-09-20 19:51:37

vous pouvez écrire cela plus succinctement en profitant de l'observable filter méthode:

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

si vous avez des problèmes de défilement vers le haut lors de l'utilisation du matériau angulaire 2 sidenav cela aidera. La fenêtre ou le corps du document n'aura pas la barre de défilement, vous devez donc obtenir le conteneur de contenu sidenav et faire défiler cet élément, sinon essayez de faire défiler la fenêtre par défaut.

this.router.events.filter(event => event instanceof NavigationEnd)
  .subscribe(() => {
      const contentContainer = document.querySelector('.mat-sidenav-content') || this.window;
      contentContainer.scrollTo(0, 0);
});

aussi, L'angle CDK v6.x a un paquet de défilement maintenant qui pourrait aider à gérer le défilement.

16
répondu mtpultz 2018-07-25 16:17:40

si vous avez le rendu côté serveur, vous devez faire attention à ne pas exécuter le code en utilisant windows sur le serveur, où cette variable n'existe pas. Il en résulterait une rupture de code.

export class AppComponent implements {
  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 est une fonction utilisée pour vérifier si la plate-forme actuelle où l'application est rendu est un navigateur ou pas. On lui injecte le platformId .

il est également possible de vérifier l'existence de la variable windows , pour être sûr, comme ceci:

if (typeof window != 'undefined')
13
répondu Raptor 2018-01-14 19:33:37

à partir de L'angle 6.1, vous pouvez maintenant éviter les tracas et passer extraOptions à votre RouterModule.forRoot() comme deuxième paramètre et peut spécifier

scrollPositionRestoration: enabled

pour dire à Angular de faire défiler vers le haut chaque fois que la route change.

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

Angulaire Officiel Docs

11
répondu Abdul Rafay 2018-09-01 10:05:08

just do it facile avec cliquez sur l'action

dans votre composant principal html faites référence #scrollContainer

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

dans l'élément principal .ts

onActivate(e, scrollContainer) {
    scrollContainer.scrollTop = 0;
}
9
répondu SAT 2017-06-13 04:26:52

la meilleure réponse réside dans la discussion angulaire de GitHub ( changer de route ne fait pas défiler vers le haut dans la nouvelle page ).

peut - être que vous voulez aller en haut seulement dans les changements de routeur racine (pas chez les enfants, parce que vous pouvez charger des routes avec la charge paresseuse dans F. E. a tabset)

app.composant.html

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

app.composant.ts

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

Full les crédits de JoniJnm ( post original )

6
répondu zurfyx 2017-08-25 19:31:50

vous pouvez ajouter le AfterViewInit lifecycle hook à votre composant.

ngAfterViewInit() {
   window.scrollTo(0, 0);
}
4
répondu stillatmylinux 2017-12-02 20:05:43

Voici une solution que j'ai trouvé. J'ai jumelé la stratégie de localisation avec les événements du routeur. Utiliser la LocationStrategy pour définir un booléen pour savoir quand un utilisateur traverse actuellement l'histoire du navigateur. De cette façon, je n'ai pas à stocker un tas de données URL et y-scroll (qui ne fonctionne pas bien de toute façon, puisque chaque donnée est remplacée sur la base de L'URL). Ceci résout également le cas de bord lorsqu'un utilisateur décide de tenir le bouton précédent ou suivant sur un navigateur et va en arrière ou en avant plusieurs pages plutôt qu'une.

P.S. Je n'ai testé que la dernière version D'IE, Chrome, FireFox, Safari, et Opera (à partir de ce post).

Espérons que cette aide.

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;
      }
    });
  }
}
3
répondu Sal_Vader_808 2018-04-15 06:39:13

cette solution est basée sur la solution de @FernandoEcheverria et @Guilhermemeirles, mais elle est plus concise et fonctionne avec les mécanismes popstate que le routeur angulaire fournit. Cela permet de stocker et de restaurer le niveau de défilement de plusieurs navigations consécutives.

nous stockons les positions de défilement pour chaque État de navigation dans une carte scrollLevels . Une fois qu'il ya un événement popstate, l'ID de l'etat qui est sur le point d'être restauré est fourni par le Routeur Angulaire: event.restoredState.navigationId . C'est ensuite utilisé pour obtenir la dernière défilement niveau de cet état de scrollLevels .

S'il n'y a pas de niveau de défilement stocké pour la route, il va défiler vers le haut comme vous vous y attendriez.

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

    });
  }

}
2
répondu Simon Mathewson 2018-07-10 23:16:36

Si vous avez besoin simplement de faire défiler la page vers le haut, vous pouvez le faire (pas la meilleure solution, mais rapide)

document.getElementById('elementId').scrollTop = 0;
1
répondu Aliaksei 2017-09-28 17:15:15

pour iphone / iOS safari vous pouvez envelopper avec un setTimeout

setTimeout(function(){
    window.scrollTo(0, 1);
}, 0);
0
répondu tubbsy 2017-03-08 12:46:05

@Fernando Echeverria grand! mais ce code ne fonctionne pas dans hachage routeur ou paresseux routeur. parce qu'ils ne déclenchent pas de modifications de l'emplacement. peut essayer ceci:

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]) {
        return
      }
      window.scrollTo(0, 0)
    }
  })
}
0
répondu Witt Bulter 2017-09-01 18:42:05

L'Utilisation du Router lui-même causera des problèmes que vous ne pouvez pas complètement surmonter pour maintenir une expérience de navigateur cohérente. À mon avis, la meilleure méthode est juste d'utiliser une coutume directive et laisser cela réinitialiser le rouleau sur le clic. La bonne chose à ce sujet, c'est que si vous êtes sur la même url que vous cliquez dessus, la page va défiler l'écran vers le haut. Ceci est compatible avec les sites Web normaux. Le directive de base pourrait ressembler à quelque chose comme ceci:

import {Directive, HostListener} from '@angular/core';

@Directive({
    selector: '[linkToTop]'
})
export class LinkToTopDirective {

    @HostListener('click')
    onClick(): void {
        window.scrollTo(0, 0);
    }
}

avec l'usage suivant:

<a routerLink="/" linkToTop></a>

ce sera suffisant pour la plupart des cas d'utilisation, mais je peux imaginer quelques questions qui peuvent

  • ne fonctionne pas sur universal en raison de l'utilisation de window
  • impact à petite vitesse sur la détection de changement, car il est déclenché par chaque clic
  • Aucun moyen de désactiver cette directive

il est en fait assez facile de surmonter ces problèmes:

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

cela prend en compte la plupart des cas d'utilisation, avec le même usage que le cas de base, avec l'avantage de l'activer/le désactiver:

<a routerLink="/" linkToTop></a> <!-- always active -->
<a routerLink="/" [linkToTop]="isActive"> <!-- active when `isActive` is true -->

publicités, ne pas lire si vous ne voulez pas être annoncé

une autre amélioration pourrait être apportée pour vérifier si le navigateur prend en charge passive des événements. Cela va compliquer le code un peu plus, et c'est un peu obscur si vous souhaitez mettre dans votre personnalisé directives/modèles. C'est pourquoi j'ai écrit un petit bibliothèque que vous pouvez utiliser pour résoudre ces problèmes. Pour avoir la même fonctionnalité que ci-dessus, et avec l'ajout de l'événement passive , vous pouvez modifier votre directive en celle-ci, si vous utilisez la bibliothèque ng-event-options . La logique est à l'intérieur du click.pnb auditeur:

@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);
      }        
    }
}
0
répondu PierreDuc 2017-12-18 11:05:59

Salut Les gars, ça marche pour moi en angle 4. Vous avez juste à référencer le parent pour faire défiler sur le changement de routeur '

mise en page.composant.pug

.wrapper(#outlet="")
    router-outlet((activate)='routerActivate($event,outlet)')

mise en page.composant.ts

 public routerActivate(event,outlet){
    outlet.scrollTop = 0;
 }`
0
répondu Jonas Arguelles 2018-01-05 09:49:39

cela a fonctionné pour moi le mieux pour tous les changements de navigation, y compris la navigation de hachage

constructor(private route: ActivatedRoute) {}

ngOnInit() {
  this._sub = this.route.fragment.subscribe((hash: string) => {
    if (hash) {
      const cmp = document.getElementById(hash);
      if (cmp) {
        cmp.scrollIntoView();
      }
    } else {
      window.scrollTo(0, 0);
    }
  });
}
0
répondu Jorg Janke 2018-01-24 20:10:54

l'idée principale derrière ce code est de garder toutes les urls visitées ainsi que les données scrollY respectives dans un tableau. Chaque fois qu'un utilisateur abandonne une page (NavigationStart), ce tableau est mis à jour. Chaque fois qu'un utilisateur entre dans une nouvelle page (NavigationEnd), nous décidons de restaurer la position Y ou ne dépend pas de la façon dont nous arrivons à cette page. Si une référence sur une page a été utilisée, nous la faisons défiler à 0. Si les fonctions de retour/avant du navigateur ont été utilisées, nous faisons défiler jusqu'à y sauvegardé dans notre tableau. Désolé pour mon anglais :)

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Location, PopStateEvent } from '@angular/common';
import { Router, Route, RouterLink, NavigationStart, NavigationEnd, 
    RouterEvent } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';

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

  private _subscription: Subscription;
  private _scrollHistory: { url: string, y: number }[] = [];
  private _useHistory = false;

  constructor(
    private _router: Router,
    private _location: Location) {
  }

  public ngOnInit() {

    this._subscription = this._router.events.subscribe((event: any) => 
    {
      if (event instanceof NavigationStart) {
        const currentUrl = (this._location.path() !== '') 
           this._location.path() : '/';
        const item = this._scrollHistory.find(x => x.url === currentUrl);
        if (item) {
          item.y = window.scrollY;
        } else {
          this._scrollHistory.push({ url: currentUrl, y: window.scrollY });
        }
        return;
      }
      if (event instanceof NavigationEnd) {
        if (this._useHistory) {
          this._useHistory = false;
          window.scrollTo(0, this._scrollHistory.find(x => x.url === 
          event.url).y);
        } else {
          window.scrollTo(0, 0);
        }
      }
    });

    this._subscription.add(this._location.subscribe((event: PopStateEvent) 
      => { this._useHistory = true;
    }));
  }

  public ngOnDestroy(): void {
    this._subscription.unsubscribe();
  }
}
0
répondu Vladimir Turygin 2018-05-17 09:58:55

à partir de l'angle 6.1, Le routeur fournit une option de configuration appelé scrollPositionRestoration , ce qui est conçu pour répondre à ce scénario.

imports: [
  RouterModule.forRoot(routes, {
    scrollPositionRestoration: 'enabled'
  }),
  ...
]
0
répondu Marty A 2018-09-11 23:32:32