Le changement d'itinéraire ne défile pas en haut de la nouvelle page

271

J'ai trouvé un comportement indésirable, du moins pour moi, lorsque l'itinéraire change. À l'étape 11 du didacticiel http://angular.github.io/angular-phonecat/step-11/app/#/phones, vous pouvez voir la liste des téléphones. Si vous faites défiler vers le bas et cliquez sur l'un des derniers, vous pouvez voir que le défilement n'est pas en haut, mais plutôt au milieu.

J'ai également trouvé cela dans l'une de mes applications et je me demandais comment puis-je faire défiler vers le haut. Je peux le faire manuellement, mais je pense qu'il devrait y avoir une autre manière élégante de le faire que je ne connais pas.

Alors, existe-t-il un moyen élégant de faire défiler vers le haut lorsque l'itinéraire change?

Matias Gonzalez
la source

Réponses:

579

Le problème est que votre ngView conserve la position de défilement lors du chargement d'une nouvelle vue. Vous pouvez demander $anchorScrollde "faire défiler la fenêtre après la mise à jour de la vue" (les documents sont un peu vagues, mais faire défiler ici signifie faire défiler vers le haut de la nouvelle vue).

La solution consiste à ajouter autoscroll="true"à votre élément ngView:

<div class="ng-view" autoscroll="true"></div>
Konrad Kiss
la source
1
Cela fonctionne pour moi, la page défile bien vers le haut, mais j'utilise scrollTo avec la directive smoothScroll, existe-t-il un moyen de faire un défilement fluide vers le haut?, En outre, votre solution fait défiler vers le haut et non vers la page. Haut de la page?
xzegga
19
L'état des documents Si l'attribut est défini sans valeur, activez le défilement . Vous pouvez donc raccourcir le code juste <div class="ng-view" autoscroll></div>et cela fonctionne de la même manière (sauf si vous vouliez rendre le défilement conditionnel).
Daniel Liuzzi
7
cela ne fonctionne pas pour moi, le doc ne dit pas qu'il
défilera
6
Je trouve que si le défilement automatique est défini sur true, puis lorsqu'un utilisateur `` revient '', il revient en haut de la page, pas là où il l'a laissée, ce qui est un comportement indésirable.
axzr
4
cela fera défiler votre vue vers cette div. Il ne défilera donc en haut de la page que s'il se trouve en haut de la page. Sinon, vous devrez exécuter $window.scrollTo(0,0)dans l'écouteur d'événements $ stateChangeSuccess
Filip Sobczak
46

Il suffit de mettre ce code à exécuter

$rootScope.$on("$routeChangeSuccess", function (event, currentRoute, previousRoute) {

    window.scrollTo(0, 0);

});
xmaster
la source
6
$window.scrollTop(0,0)fonctionne mieux pour moi que le défilement automatique. Dans mon cas, j'ai des vues qui entrent et sortent avec des transitions.
IsidroGH
1
Cela fonctionne également mieux pour moi que autoscroll = "true". Le défilement automatique était trop tard pour une raison quelconque, il défilerait vers le haut une fois la nouvelle vue déjà visible.
Gregory Bolkenstijn
5
Cela a fonctionné le mieux pour moi: $ rootScope. $ On ('$ stateChangeSuccess', function () {$ ("html, body"). Animate ({scrollTop: 0}, 200);});
James Gentes
$ window.scrollTop devrait être $ window.scrollTo, sinon il dit qu'il n'existe pas. Cette solution fonctionne très bien!
Software Prophets
@JamesGentes j'avais aussi besoin de celui-ci. Merci.
Mackenzie Browne
36

Ce code a très bien fonctionné pour moi .. J'espère qu'il fonctionnera également très bien pour vous .. Tout ce que vous avez à faire est d'injecter simplement $ anchorScroll à votre bloc d'exécution et d'appliquer la fonction d'écoute au rootScope comme je l'ai fait dans l'exemple ci-dessous ..

 angular.module('myAngularApp')
.run(function($rootScope, Auth, $state, $anchorScroll){
    $rootScope.$on("$locationChangeSuccess", function(){
        $anchorScroll();
    });

Voici l'ordre d'appel du module Angularjs:

  1. app.config()
  2. app.run()
  3. directive's compile functions (if they are found in the dom)
  4. app.controller()
  5. directive's link functions (again, if found)

RUN BLOCK est exécuté après la création de l'injecteur et est utilisé pour démarrer l'application .. cela signifie que lorsque vous êtes redirigé vers la nouvelle vue de route, l'écouteur du bloc d'exécution appelle le

$ anchorScroll ()

et vous pouvez maintenant voir le défilement commencer vers le haut avec la nouvelle vue routée :)

Rizwan Jamal
la source
Enfin, une solution qui fonctionne avec des en-têtes collants! Je vous remercie!
Fabio
31

Après une heure ou deux d'essayer toutes les combinaisons de ui-view autoscroll=true, $stateChangeStart, $locationChangeStart, $uiViewScrollProvider.useAnchorScroll(), $provide('$uiViewScroll', ...), et bien d' autres, je ne pouvais pas défiler à haut sur la page nouvelle au travail comme prévu.

C'est finalement ce qui a fonctionné pour moi. Il capture pushState et replaceState et ne met à jour la position de défilement que lorsque de nouvelles pages sont parcourues (les boutons Précédent / Suivant conservent leurs positions de défilement):

.run(function($anchorScroll, $window) {
  // hack to scroll to top when navigating to new URLS but not back/forward
  var wrap = function(method) {
    var orig = $window.window.history[method];
    $window.window.history[method] = function() {
      var retval = orig.apply(this, Array.prototype.slice.call(arguments));
      $anchorScroll();
      return retval;
    };
  };
  wrap('pushState');
  wrap('replaceState');
})
wkonkel
la source
Cela fonctionne très bien pour mon cas d'utilisation. J'utilise Angular UI-Router avec des vues imbriquées (plusieurs niveaux de profondeur). Merci!
Joshua Powell
Pour info - vous devez être en mode HTML5 en angulaire pour utiliser pushState: $locationProvider.html5Mode(true); @wkronkel existe-t-il un moyen d'accomplir cela lorsque vous n'utilisez pas le mode HTML5 en angulaire? Le rechargement de la page génère des erreurs 404 en raison de l'
activation du
@britztopher Vous pouvez vous connecter aux événements intégrés et maintenir votre propre histoire, non?
Casey
2
où ajoutez-vous cela .run? l'angulaire de votre appobjet?
Philll_t
1
@Philll_t: Oui, la runfonction est une méthode disponible sur chaque objet module angulaire.
Hinrich
18

Voici ma solution (apparemment) robuste, complète et (assez) concise. Il utilise le style compatible avec la minification (et l'accès angular.module (NAME) à votre module).

angular.module('yourModuleName').run(["$rootScope", "$anchorScroll" , function ($rootScope, $anchorScroll) {
    $rootScope.$on("$locationChangeSuccess", function() {
                $anchorScroll();
    });
}]);

PS J'ai trouvé que la fonction de défilement automatique n'avait aucun effet, qu'elle soit définie sur true ou false.

James
la source
1
Hmmm .. de faire défiler la vue vers le haut d' abord , puis la vue change sur l'écran pour moi.
Strelok
Solution propre que je cherchais. Si vous cliquez sur "Retour", cela vous amène là où vous vous étiez arrêté - cela pourrait ne pas être souhaitable pour certains, mais j'aime ça dans mon cas.
mjoyce91
15

En utilisant angularjs UI Router, ce que je fais est le suivant:

    .state('myState', {
        url: '/myState',
        templateUrl: 'app/views/myState.html',
        onEnter: scrollContent
    })

Avec:

var scrollContent = function() {
    // Your favorite scroll method here
};

Il n'échoue jamais sur aucune page et n'est pas global.

Léon Pelletier
la source
1
Excellente idée, vous pouvez le raccourcir en utilisant:onEnter: scrollContent
zucker
2
Que mettez-vous là où vous avez écrit // Your favorite scroll method here?
Agent Zebra
4
Bon timing: j'étais deux lignes au-dessus de ce commentaire dans le code d'origine, essayant ui-router-title . J'utilise $('html, body').animate({ scrollTop: -10000 }, 100);
Léon Pelletier
1
Haha, super! Etrange mais ça ne marche pas pour moi tout le temps. J'ai des vues qui sont si courtes qu'elles n'ont pas de défilement, et deux vues qui ont un défilement, lorsque je place onEnter: scrollContentsur chaque vue, cela ne fonctionne que sur la première vue / route, pas sur la deuxième. Étrange.
Agent Zebra
1
Je ne suis pas sûr, non, ça ne défile pas tout en haut quand ça devrait. Lorsque je clique entre les «itinéraires», certains d'entre eux défilent toujours vers l'emplacement de la vue ng, plutôt que vers le haut absolu de la page englobante. Cela a-t-il du sens?
Agent Zebra
13

Pour info pour toute personne rencontrant le problème décrit dans le titre (comme je l'ai fait) qui utilise également le plugin AngularUI Router ...

Comme demandé et répondu dans cette question SO, le routeur angular-ui saute au bas de la page lorsque vous changez de route.
Vous ne pouvez pas comprendre pourquoi la page se charge en bas? Problème de défilement automatique de l'interface utilisateur angulaire

Cependant, comme l'indique la réponse, vous pouvez désactiver ce comportement en disant autoscroll="false"sur votre ui-view.

Par exemple:

<div ui-view="pagecontent" autoscroll="false"></div>
<div ui-view="sidebar" autoscroll="false"></div> 

http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.directive:ui-view

mg1075
la source
16
Le mien ne saute pas vers le bas, il conserve simplement la position de défilement au lieu d'aller en haut.
Stephen Smith
5
Cela ne répond pas à la question. J'ai le même problème - lorsque l'itinéraire change, le niveau de défilement reste au même endroit au lieu de défiler vers le haut. Je peux le faire défiler vers le haut, mais uniquement en changeant le hachage, ce que je ne veux pas faire pour des raisons évidentes.
Will
7

vous pouvez utiliser ce javascript

$anchorScroll()
CodeIsLife
la source
3
Eh bien, ce n'est pas si simple dans angularjs. Votre solution n'est valable qu'en jquery. Ce que je recherche, c'est une manière élégante de le faire dans angularjs
Matias Gonzalez
4
Avez-vous essayé d'utiliser $ anchorScroll (), c'est documenté docs.angularjs.org/api/ng.%24anchorScroll .
CodeIsLife
1
FYI Si vous spécifiez un emplacement avec $location.hash('top')avant $anchorScroll()les boutons de navigation avant / arrière par défaut ne fonctionnent plus; ils continuent de vous rediriger vers le hachage dans l'URL
Roy
7

Si vous utilisez ui-router, vous pouvez utiliser (en cours d'exécution)

$rootScope.$on("$stateChangeSuccess", function (event, currentState, previousState) {
    $window.scrollTo(0, 0);
});
Colzak
la source
Je sais que cela devrait fonctionner, j'utilise beaucoup "$ stateChangeSuccess", mais pour une raison quelconque, $ window.scrollTo (0,0) ne fonctionne pas avec cela.
Abhas
4

J'ai trouvé cette solution. Si vous passez à une nouvelle vue, la fonction est exécutée.

var app = angular.module('hoofdModule', ['ngRoute']);

    app.controller('indexController', function ($scope, $window) {
        $scope.$on('$viewContentLoaded', function () {
            $window.scrollTo(0, 0);
        });
    });
Vince Verhoeven
la source
3

Définir autoScroll sur true n'a pas été un truc pour moi, j'ai donc choisi une autre solution. J'ai créé un service qui se connecte à chaque fois que l'itinéraire change et qui utilise le service $ anchorScroll intégré pour faire défiler vers le haut. Travaille pour moi :-).

Un service:

 (function() {
    "use strict";

    angular
        .module("mymodule")
        .factory("pageSwitch", pageSwitch);

    pageSwitch.$inject = ["$rootScope", "$anchorScroll"];

    function pageSwitch($rootScope, $anchorScroll) {
        var registerListener = _.once(function() {
            $rootScope.$on("$locationChangeSuccess", scrollToTop);
        });

        return {
            registerListener: registerListener
        };

        function scrollToTop() {
            $anchorScroll();
        }
    }
}());

Enregistrement:

angular.module("mymodule").run(["pageSwitch", function (pageSwitch) {
    pageSwitch.registerListener();
}]);
asp_net
la source
3

Un peu tard pour la fête, mais j'ai essayé toutes les méthodes possibles, et rien n'a fonctionné correctement. Voici ma solution élégante:

J'utilise un contrôleur qui régit toutes mes pages avec ui-router. Cela me permet de rediriger les utilisateurs qui ne sont pas authentifiés ou validés vers un emplacement approprié. La plupart des gens mettent leur middleware dans la configuration de leur application, mais j'avais besoin de quelques requêtes http, donc un contrôleur global fonctionne mieux pour moi.

Mon index.html ressemble à:

<main ng-controller="InitCtrl">
    <nav id="top-nav"></nav>
    <div ui-view></div>
</main>

Mon initCtrl.js ressemble à:

angular.module('MyApp').controller('InitCtrl', function($rootScope, $timeout, $anchorScroll) {
    $rootScope.$on('$locationChangeStart', function(event, next, current){
        // middleware
    });
    $rootScope.$on("$locationChangeSuccess", function(){
        $timeout(function() {
            $anchorScroll('top-nav');
       });
    });
});

J'ai essayé toutes les options possibles, cette méthode fonctionne le mieux.

BuffMcBigHuge
la source
1
C'est le seul qui a fonctionné avec un matériau angulaire pour moi, il était également important de placer l' id="top-nav"élément sur l'élément correct.
BatteryAcid du
2

Toutes les réponses ci-dessus interrompent le comportement attendu du navigateur. Ce que la plupart des gens veulent, c'est quelque chose qui défilera vers le haut s'il s'agit d'une "nouvelle" page, mais reviendra à la position précédente si vous y arrivez via le bouton Précédent (ou Suivant).

Si vous assumez le mode HTML5, cela se révèle facile (même si je suis sûr que certaines personnes brillantes peuvent trouver comment rendre cela plus élégant!):

// Called when browser back/forward used
window.onpopstate = function() { 
    $timeout.cancel(doc_scrolling); 
};

// Called after ui-router changes state (but sadly before onpopstate)
$scope.$on('$stateChangeSuccess', function() {
    doc_scrolling = $timeout( scroll_top, 50 );

// Moves entire browser window to top
scroll_top = function() {
    document.body.scrollTop = document.documentElement.scrollTop = 0;
}

La façon dont cela fonctionne est que le routeur suppose qu'il va défiler vers le haut, mais tarde un peu pour donner au navigateur une chance de terminer. Si le navigateur nous informe alors que le changement est dû à une navigation en arrière / en avant, il annule le délai d'attente et le défilement ne se produit jamais.

J'ai utilisé des documentcommandes brutes pour faire défiler, car je veux passer en haut de la fenêtre. Si vous voulez juste que votre ui-viewdéfilement, définissez autoscroll="my_var"où vous contrôlez en my_varutilisant les techniques ci-dessus. Mais je pense que la plupart des gens voudront faire défiler la page entière si vous allez sur la page comme "nouvelle".

Ce qui précède utilise ui-router, bien que vous puissiez utiliser ng-route à la place en échangeant $routeChangeSuccesspour $stateChangeSuccess.

Dev93
la source
Comment mettez-vous cela en œuvre? Où le mettriez-vous dans le code des routes régulières angulaires-ui comme le suivant? var routerApp = angular.module('routerApp', ['ui.router']); routerApp.config(function($stateProvider, $urlRouterProvider, $locationProvider) { $urlRouterProvider.otherwise('/home'); $locationProvider.html5Mode(false).hashPrefix(""); $stateProvider // HOME STATES AND NESTED VIEWS ======================================== .state('home', { url: '/home', templateUrl: 'partial-home.html' }) });
Agent Zebra
hmmm ... n'a pas fonctionné :-( Ça tue juste l'application. .state('web', { url: '/webdev', templateUrl: 'partial-web.html', controller: function($scope) { $scope.clients = ['client1', 'client2', 'client3']; // I put it here } }) J'apprends juste ce truc, peut-être que je manque quelque chose: D
Agent Zebra
@AgentZebra Au minimum, le contrôleur devra prendre $ timeout en entrée (puisque mon code y fait référence), mais peut-être plus important encore, le contrôleur que je mentionne doit vivre à un niveau supérieur à un état individuel (vous pourrait inclure les instructions dans votre contrôleur de niveau supérieur). La courbe d'apprentissage AngularJS initiale est en effet très difficile; bonne chance!
Dev93
2

Aucune des réponses fournies n'a résolu mon problème. J'utilise une animation entre les vues et le défilement se produira toujours après l'animation. La solution que j'ai trouvée pour que le défilement vers le haut se produise avant l'animation est la directive suivante:

yourModule.directive('scrollToTopBeforeAnimation', ['$animate', function ($animate) {
    return {
        restrict: 'A',
        link: function ($scope, element) {
            $animate.on('enter', element, function (element, phase) {

                if (phase === 'start') {

                    window.scrollTo(0, 0);
                }

            })
        }
    };
}]);

Je l'ai inséré à mon avis comme suit:

<div scroll-to-top-before-animation>
    <div ng-view class="view-animation"></div>
</div>
aBertrand
la source
2

Solution simple, ajoutez scrollPositionRestorationle module de route principal activé.
Comme ça:

const routes: Routes = [

 {
   path: 'registration',
   loadChildren: () => RegistrationModule
},
];

 @NgModule({
  imports: [
   RouterModule.forRoot(routes,{scrollPositionRestoration:'enabled'})
  ],
 exports: [
 RouterModule
 ]
 })
  export class AppRoutingModule { }
Farida Anjum
la source
0

J'ai enfin obtenu ce dont j'avais besoin.

Je devais faire défiler vers le haut, mais je voulais que certaines transitions ne

Vous pouvez contrôler cela au niveau itinéraire par itinéraire.
Je combine la solution ci-dessus par @wkonkel et j'ajoute un simplenoScroll: true paramètre à certaines déclarations de route. Ensuite, je l'attrape dans la transition.

Dans l'ensemble: cela flotte en haut de la page sur les nouvelles transitions, il ne flotte pas en haut sur Forward/ Backtransitions, et il vous permet de remplacer ce comportement si nécessaire.

Le code: (solution précédente plus une option noScroll supplémentaire)

  // hack to scroll to top when navigating to new URLS but not back/forward
  let wrap = function(method) {
    let orig = $window.window.history[method];
    $window.window.history[method] = function() {
      let retval = orig.apply(this, Array.prototype.slice.call(arguments));
      if($state.current && $state.current.noScroll) {
        return retval;
      }
      $anchorScroll();
      return retval;
    };
  };
  wrap('pushState');
  wrap('replaceState');

Mettez ça dans votre app.runbloc et injectez $state...myApp.run(function($state){...})

Ensuite, si vous ne souhaitez pas faire défiler vers le haut de la page, créez un itinéraire comme celui-ci:

.state('someState', {
  parent: 'someParent',
  url: 'someUrl',
  noScroll : true // Notice this parameter here!
})
Augie Gardner
la source
0

Cela a fonctionné pour moi, y compris le défilement automatique

<div class="ngView" autoscroll="true" >

Teja
la source