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?
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)
});
}
}
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);
}
});
}
}
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.
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')
à 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',
})
],
...
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;
}
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 )
vous pouvez ajouter le AfterViewInit lifecycle hook à votre composant.
ngAfterViewInit() {
window.scrollTo(0, 0);
}
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;
}
});
}
}
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);
}
}
});
}
}
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;
pour iphone / iOS safari vous pouvez envelopper avec un setTimeout
setTimeout(function(){
window.scrollTo(0, 1);
}, 0);
@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)
}
})
}
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 dewindow
- 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);
}
}
}
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;
}`
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);
}
});
}
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();
}
}
à 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'
}),
...
]