Angular 5 Faites défiler vers le haut à chaque clic sur l'itinéraire


103

J'utilise angular 5. J'ai un tableau de bord où j'ai quelques sections avec un petit contenu et quelques sections avec un contenu si grand que je suis confronté à un problème lors du changement de routeur en allant en haut. Chaque fois que j'ai besoin de faire défiler pour aller en haut. Quelqu'un peut-il m'aider à résoudre ce problème afin que lorsque je change de routeur, ma vue reste toujours au sommet.

Merci d'avance.


Réponses:


218

Il existe quelques solutions, assurez-vous de toutes les vérifier :)


La sortie du routeur émettra l' activateévénement à chaque fois qu'un nouveau composant est instancié, nous pourrions donc utiliser (activate)pour faire défiler (par exemple) vers le haut:

app.component.html

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

app.component.ts

onActivate(event) {
    window.scroll(0,0);
    //or document.body.scrollTop = 0;
    //or document.querySelector('body').scrollTo(0,0)
    ...
}

Utilisez, par exemple, cette solution pour un défilement fluide:

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

Si vous souhaitez être sélectif, disons que tous les composants ne doivent pas déclencher le défilement, vous pouvez le vérifier dans une ifinstruction comme celle-ci:

onActivate(e) {
    if (e.constructor.name)==="login"{ // for example
            window.scroll(0,0);
    }
}

Depuis Angular6.1, nous pouvons également utiliser { scrollPositionRestoration: 'enabled' }sur des modules chargés avec impatience et il sera appliqué à toutes les routes:

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

Il fera également déjà le défilement fluide. Cependant, cela présente l'inconvénient de le faire sur chaque routage.


Une autre solution consiste à faire le défilement supérieur sur l'animation du routeur. Ajoutez ceci dans chaque transition où vous souhaitez faire défiler vers le haut:

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

4
ça marche . Merci pour votre réponse. vous gagnez beaucoup de temps pour moi
raihan

2
les événements de défilement sur l' windowobjet ne fonctionnent pas en angulaire 5. Des suppositions pourquoi?
Sahil Babbar

2
Un vrai héros ici. A sauvé des heures de frustration. Je vous remercie!
Anjana Silva

1
@AnjanaSilva, merci pour le commentaire positif! Je suis très heureux que cela puisse vous aider :)
Vega

2
Existe-t-il un moyen pour le module chargé paresseusement?
Pankaj Prakash

54

Si vous rencontrez ce problème dans Angular 6, vous pouvez le résoudre en ajoutant le paramètre scrollPositionRestoration: 'enabled'au RouterModule de app-routing.module.ts:

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

7
Veuillez ne pas publier de photos de code. Copiez-collez simplement le code lui-même dans votre réponse.
mypetlion

2
Sûr. La prochaine fois, je le ferai. Je suis juste un peu nouveau ici. :)
Nimezzz

4
Notez qu'à partir du 6 novembre 2019 en utilisant Angular 8, au moins, la scrollPositionRestorationpropriété ne fonctionne pas avec le contenu de page dynamique (c'est-à-dire lorsque le contenu de la page est chargé de manière asynchrone): voir ce rapport de bogue Angular: github.com/angular/angular / issues / 24547
dbeachy1

25

EDIT: Pour Angular 6+, veuillez utiliser la réponse de Nimesh Nishara Indimagedara en mentionnant:

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

Réponse originale:

Si tout échoue, créez un élément HTML vide (par exemple: div) en haut (ou faites défiler jusqu'à l'emplacement souhaité) avec id = "top" sur le modèle (ou le modèle parent):

<div id="top"></div>

Et en composant:

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

2
Cette solution a fonctionné pour moi (testée sur Chrome ainsi que Edge). La solution acceptée n'a pas fonctionné pour mon projet (Angular5)
Rob van Meeuwen

@RobvanMeeuwen, Si ma réponse n'a pas fonctionné, vous ne l'avez probablement pas mise en œuvre de la même manière. Cette solution manipule directement le DOM qui n'est pas correct non plus en toute sécurité
Vega

@Vega, c'est pourquoi je l'ai appelé un hack. Votre solution est la bonne. Certaines personnes ici n'ont pas pu implémenter le vôtre, alors j'ai proposé le hack de secours. Ils doivent refactoriser leur code en fonction de la version sur laquelle ils se trouvent à ce stade.
GeoRover

De toute cette solution est un travail pour moi. Merci @GeoRover
Gangani Roshan

Pour Angular 6+, veuillez utiliser la réponse de Nimesh Nishara Indimagedara.
GeoRover


6

Bien que @Vega fournisse la réponse directe à votre question, il y a des problèmes. Il casse le bouton Précédent / Suivant du navigateur. Si votre utilisateur clique sur le bouton Précédent ou Suivant du navigateur, il perd sa place et se fait défiler en haut. Cela peut être un peu pénible pour vos utilisateurs s'ils devaient faire défiler vers le bas pour accéder à un lien et décidaient de cliquer en arrière uniquement pour trouver que la barre de défilement avait été réinitialisée en haut.

Voici ma solution au problème.

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
Merci pour le code avancé et la bonne solution. Mais parfois, la solution @Vega est meilleure, car elle résout de nombreux problèmes d'animation et de hauteur de page dynamique. Votre solution est bonne si vous avez une longue page avec du contenu et une animation de routage simple. Je l'essaie sur la page avec de nombreux blocs d'animation et de dynamique et ça n'a pas l'air si bon. Je pense que parfois nous pouvons sacrifier la «position arrière» pour notre application. Mais sinon - votre solution est la meilleure que je vois pour Angular. Merci encore
Denis Savenko

5

Dans mon cas, je viens d'ajouter

window.scroll(0,0);

dans ngOnInit()et son fonctionnement très bien.


4

À partir de Angular Version 6+ Pas besoin d'utiliser window.scroll (0,0)

Pour la version angulaire 6+de @ Représente les options de configuration du routeur.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'
}

On peut utiliser scrollPositionRestoration?: 'disabled' | 'enabled' | 'top'dans

Exemple:

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

Et si l'on a besoin de contrôler manuellement le défilement, pas besoin d'utiliser à la window.scroll(0,0) place du package commun 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
}

L'utilisation est assez simple Exemple:

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

4

si vous utilisez mat-sidenav, donnez un identifiant à la sortie du routeur (si vous avez des sorties de routeur parent et enfant) et utilisez la fonction activate <router-outlet id="main-content" (activate)="onActivate($event)"> et utilisez ce sélecteur de requête `` mat-sidenav-content '' pour faire défiler vers le haut onActivate(event) { document.querySelector("mat-sidenav-content").scrollTo(0, 0); }


Fonctionne très bien même sans utiliser un id (j'en ai un router-outletsur mon application). Je l'ai aussi fait d'une manière plus `` angulaire '': @ViewChild(MatSidenavContainer) sidenavContainer: MatSidenavContainer; onActivate() { this.sidenavContainer.scrollable.scrollTo({ left: 0, top: 0 }); }
GCSDC

3

Je continue de chercher une solution intégrée à ce problème, comme dans AngularJS. Mais jusque-là, cette solution fonctionne pour moi, c'est simple et préserve la fonctionnalité du bouton de retour.

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

Réponse de zurfyx original post


3

Il vous suffit de créer une fonction qui contient le réglage du défilement de votre écran

par exemple

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

window.scrollTo (xpos, ypos) -> paramètre attendu.


2

Voici une solution qui ne défile vers le haut du composant que lors de la première visite pour CHAQUE composant (au cas où vous auriez besoin de faire quelque chose de différent par composant):

Dans chaque composant:

export class MyComponent implements OnInit {

firstLoad: boolean = true;

...

ngOnInit() {

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

2

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

}


2

Angular 6.1 et versions ultérieures:

Vous pouvez utiliser la solution intégrée disponible dans Angular 6.1+ avec option scrollPositionRestoration: 'enabled'pour obtenir la même chose.

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

Angular 6.0 et versions antérieures:

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

Remarque: le comportement attendu est que lorsque vous revenez à la page, elle doit rester défilée vers le bas jusqu'au même emplacement que lorsque vous avez cliqué sur le lien, mais défiler vers le haut lorsque vous arrivez sur chaque page.



2

Pour quelqu'un qui recherche une fonction de défilement, ajoutez simplement la fonction et appelez-la quand vous en avez besoin

scrollbarTop(){

  window.scroll(0,0);
}

1

Essaye ça:

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

1

Composant: Abonnez-vous à tous les événements de routage au lieu de créer une action dans le modèle et faites défiler NavigationEnd b / c, sinon vous le déclencherez sur de mauvaises navigations ou des routes bloquées, etc. C'est un moyen sûr de savoir que si une route est parcourue avec succès, puis faites défiler. Sinon, ne faites rien.

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

1

essaye ça

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

ce code pris en charge angulaire 6 <=

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.