Wenn Sie die Route ändern, wird auf der neuen Seite nicht nach oben gescrollt

271

Ich habe zumindest für mich ein unerwünschtes Verhalten festgestellt, wenn sich die Route ändert. In Schritt 11 des Tutorials http://angular.github.io/angular-phonecat/step-11/app/#/phones Sie die Liste der Telefone. Wenn Sie nach unten scrollen und auf eine der neuesten klicken, können Sie sehen, dass die Schriftrolle nicht oben, sondern in der Mitte liegt.

Ich habe dies auch in einer meiner Apps gefunden und mich gefragt, wie ich dies dazu bringen kann, nach oben zu scrollen. Ich kann es manuell tun, aber ich denke, dass es einen anderen eleganten Weg geben sollte, den ich nicht kenne.

Gibt es eine elegante Möglichkeit, nach oben zu scrollen, wenn sich die Route ändert?

Matias Gonzalez
quelle

Antworten:

578

Das Problem ist, dass Ihr ngView die Bildlaufposition beibehält, wenn es eine neue Ansicht lädt. Sie können anweisen $anchorScroll, "das Ansichtsfenster nach dem Aktualisieren der Ansicht zu scrollen" (die Dokumente sind etwas vage, aber hier zu scrollen bedeutet , zum oberen Rand der neuen Ansicht zu scrollen ).

Die Lösung besteht darin autoscroll="true", Ihrem ngView-Element Folgendes hinzuzufügen :

<div class="ng-view" autoscroll="true"></div>
Konrad Kuss
quelle
1
Dies funktioniert für mich, die Seite scrollen gut nach oben, aber ich verwende scrollTo mit der Direktive glattScroll. Gibt es eine Möglichkeit, einen Smoth-Bildlauf nach oben durchzuführen? Außerdem kann Ihre Lösung nach oben scrollen, nicht zur Seite, um dorthin zu scrollen ganz oben auf der Seite?
Xzegga
19
Der Dokumentstatus Wenn das Attribut ohne Wert festgelegt ist, aktivieren Sie das Scrollen . Sie können den Code also auf "Nur" kürzen <div class="ng-view" autoscroll></div>und er funktioniert genauso (es sei denn, Sie möchten das Scrollen an Bedingungen binden).
Daniel Liuzzi
7
es funktioniert nicht für mich, der Doc sagt nicht, dass es nach oben scrollen wird
Pencilcheck
6
Ich finde, wenn Autoscroll auf true gesetzt ist, kehren Benutzer beim Zurückgehen zum Anfang der Seite zurück, nicht dort, wo sie sie verlassen haben, was unerwünschtes Verhalten ist.
Axzr
4
Dadurch wird Ihre Ansicht zu diesem Div gescrollt. Es wird also nur dann zum Anfang der Seite gescrollt, wenn es sich oben auf der Seite befindet. Andernfalls müssen Sie $window.scrollTo(0,0)in $ stateChangeSuccess Event Listener
Filip Sobczak
46

Setzen Sie einfach diesen Code zum Ausführen

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

    window.scrollTo(0, 0);

});
xmaster
quelle
6
$window.scrollTop(0,0)funktioniert bei mir besser als autoscroll. In meinem Fall habe ich Ansichten, die mit Übergängen ein- und ausgehen.
IsidroGH
1
Dies funktioniert auch besser für mich als autoscroll = "true". Autoscroll war aus irgendeinem Grund zu spät. Es wurde nach oben gescrollt, nachdem die neue Ansicht bereits sichtbar war.
Gregory Bolkenstijn
5
Dies funktionierte am besten für mich: $ rootScope. $ On ('$ stateChangeSuccess', function () {$ ("html, body"). Animate ({scrollTop: 0}, 200);});
James Gentes
$ window.scrollTop sollte $ window.scrollTo sein, andernfalls heißt es, dass es nicht existiert. Diese Lösung funktioniert super!
Software-Propheten
@ JamesGentes Ich brauchte diesen auch. Vielen Dank.
Mackenzie Browne
36

Dieser Code hat für mich großartig funktioniert. Ich hoffe, er funktioniert auch für Sie. Alles, was Sie tun müssen, ist, $ anchorScroll in Ihren Ausführungsblock zu injizieren und die Listener-Funktion auf das rootScope anzuwenden, wie ich es im folgenden Beispiel getan habe.

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

Hier ist die Aufrufreihenfolge des Angularjs-Moduls:

  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 wird ausgeführt, nachdem der Injektor erstellt wurde, und wird zum Kickstart der Anwendung verwendet. Wenn Sie zur neuen Routenansicht umleiten, ruft der Listener im Run-Block das auf

$ anchorScroll ()

und Sie können jetzt sehen, dass der Bildlauf mit der neuen gerouteten Ansicht nach oben beginnt :)

Rizwan Jamal
quelle
Endlich eine Lösung, die mit Sticky-Headern funktioniert! Danke dir!
Fabio
31

Nach einer Stunde oder zwei jede Kombination aus versuchen ui-view autoscroll=true, $stateChangeStart, $locationChangeStart, $uiViewScrollProvider.useAnchorScroll(), $provide('$uiViewScroll', ...), und vielen anderen, ich konnte nicht Scroll-to-Top-on-new-Seite an der Arbeit wie erwartet.

Das hat letztendlich bei mir funktioniert. Es erfasst pushState und replaceState und aktualisiert die Bildlaufposition nur, wenn neue Seiten aufgerufen werden (die Schaltfläche "Zurück / Vorwärts" behält ihre Bildlaufpositionen bei):

.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
quelle
Dies funktioniert hervorragend für meinen Anwendungsfall. Ich verwende Angular UI-Router mit verschachtelten Ansichten (mehrere Ebenen tief). Vielen Dank!
Joshua Powell
Zu Ihrer Information: Sie müssen sich im Winkel im HTML5-Modus befinden, um pushState verwenden zu können: $locationProvider.html5Mode(true); @wkronkel Gibt es eine Möglichkeit, dies zu erreichen, wenn Sie den HTML5-Modus nicht im Winkel verwenden? Weil das Neuladen der Seite 404 Fehler
auslöst
@britztopher Du kannst dich in die eingebauten Ereignisse einhängen und deine eigene Geschichte pflegen, oder?
Casey
2
Wo hängen Sie das .runan? der eckige dein appobjekt?
Philll_t
1
@Philll_t: Ja, die runFunktion ist eine Methode, die für jedes Winkelmodulobjekt verfügbar ist.
Hinrich
18

Hier ist meine (scheinbar) robuste, vollständige und (ziemlich) prägnante Lösung. Es verwendet den mit der Minifizierung kompatiblen Stil (und den NAME-Zugriff (Angular.module) auf Ihr Modul).

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

PS Ich fand, dass das Autoscroll-Ding keine Auswirkung hatte, ob auf wahr oder falsch gesetzt.

James
quelle
1
Hmmm .. diese scrollt die Ansicht auf den oberen ersten und dann für mich wechselt die Ansicht auf dem Bildschirm.
Strelok
Saubere Lösung, die ich gesucht habe. Wenn Sie auf "Zurück" klicken, gelangen Sie dorthin, wo Sie aufgehört haben - dies ist für manche vielleicht nicht wünschenswert, aber in meinem Fall gefällt es mir.
mjoyce91
15

Mit angularjs UI Router mache ich Folgendes:

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

Mit:

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

Es schlägt auf keiner Seite fehl und ist nicht global.

Léon Pelletier
quelle
1
Tolle Idee, Sie können es kürzer machen mit:onEnter: scrollContent
zucker
2
Was hast du dort hingelegt, wo du geschrieben hast // Your favorite scroll method here?
Agent Zebra
4
Gutes Timing: Ich war zwei Zeilen über diesem Kommentar im Originalcode und habe versucht, den UI-Router-Titel zu verwenden . Ich benutze $('html, body').animate({ scrollTop: -10000 }, 100);
Léon Pelletier
1
Haha toll! Seltsamerweise funktioniert es bei mir nicht immer. Ich habe einige Ansichten, die so kurz sind, dass sie keine Schriftrolle haben, und zwei Ansichten, die eine Schriftrolle haben. Wenn ich sie onEnter: scrollContentauf jede Ansicht platziere, funktioniert sie nur für die erste Ansicht / Route, nicht für die zweite. Seltsam.
Agent Zebra
1
Ich bin mir nicht sicher, nein, es wird einfach nicht nach oben gescrollt, wenn es sollte. Wenn ich zwischen den 'Routen' klicke, scrollen einige von ihnen immer noch dorthin, wo sich die ng-Ansicht befindet, und nicht zum absoluten oberen Rand der umschließenden Seite. Ist das sinnvoll?
Agent Zebra
13

Zu Ihrer Information für alle, die auf das im Titel beschriebene Problem stoßen (wie ich), die auch das AngularUI Router- Plugin verwenden ...

Wie in dieser SO-Frage gestellt und beantwortet, springt der Winkel-UI-Router beim Ändern der Routen zum Ende der Seite.
Sie können nicht herausfinden, warum die Seite unten geladen wird? Angular UI-Router Autoscroll-Problem

Wie in der Antwort angegeben, können Sie dieses Verhalten jedoch deaktivieren, indem Sie autoscroll="false"auf Ihrer Seite sagen ui-view.

Beispielsweise:

<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
quelle
16
Meins springt nicht nach unten, sondern behält nur die Bildlaufposition bei, anstatt nach oben zu gehen.
Stephen Smith
5
Dies beantwortet die Frage nicht. Ich habe das gleiche Problem: Wenn sich die Route ändert, bleibt die Bildlaufebene nur an derselben Stelle, anstatt nach oben zu scrollen. Ich kann es dazu bringen, nach oben zu scrollen, aber nur durch Ändern des Hashs, was ich aus offensichtlichen Gründen nicht tun möchte.
Will
7

Sie können dieses Javascript verwenden

$anchorScroll()
CodeIsLife
quelle
3
Nun, es ist nicht so einfach in Angularjs. Ihre Lösung ist nur in jquery gültig. Was ich suche, ist eine elegante Möglichkeit, dies in Angularjs zu tun
Matias Gonzalez
4
Haben Sie versucht, $ anchorScroll () zu verwenden, ist docs.angularjs.org/api/ng.%24anchorScroll dokumentiert .
CodeIsLife
1
Zu Ihrer Information Wenn Sie einen Ort mit angeben , $location.hash('top')bevor $anchorScroll()der Standard vor / zurück Browser - Buttons funktionieren nicht mehr; Sie leiten Sie immer wieder zum Hash in der URL weiter
Roy
7

Wenn Sie einen UI-Router verwenden, können Sie diesen verwenden (on run)

$rootScope.$on("$stateChangeSuccess", function (event, currentState, previousState) {
    $window.scrollTo(0, 0);
});
Colzak
quelle
Ich weiß, dass dies funktionieren sollte, ich verwende ausgiebig "$ stateChangeSuccess", aber aus irgendeinem Grund funktioniert $ window.scrollTo (0,0) damit nicht.
Abhas
4

Ich habe diese Lösung gefunden. Wenn Sie zu einer neuen Ansicht wechseln, wird die Funktion ausgeführt.

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

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

Das Setzen von autoScroll auf true war für mich nicht der Trick, daher habe ich eine andere Lösung gewählt. Ich habe einen Dienst erstellt, der sich jedes Mal einbindet, wenn sich die Route ändert, und der den integrierten Dienst $ anchorScroll verwendet, um nach oben zu scrollen. Funktioniert bei mir :-).

Bedienung:

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

Anmeldung:

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

Ein bisschen spät zur Party, aber ich habe jede mögliche Methode ausprobiert und nichts hat richtig funktioniert. Hier ist meine elegante Lösung:

Ich verwende einen Controller, der alle meine Seiten mit UI-Router steuert. Dadurch kann ich Benutzer, die nicht authentifiziert oder validiert sind, an einen geeigneten Ort umleiten. Die meisten Leute haben ihre Middleware in die Konfiguration ihrer App eingefügt, aber ich benötigte einige http-Anfragen, daher funktioniert ein globaler Controller für mich besser.

Meine index.html sieht aus wie:

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

Meine initCtrl.js sieht aus wie:

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

Ich habe jede mögliche Option ausprobiert, diese Methode funktioniert am besten.

BuffMcBigHuge
quelle
1
Dies ist die einzige, die für mich mit eckigem Material gearbeitet hat. Auch das Platzieren id="top-nav"des richtigen Elements war wichtig.
BatteryAcid
2

Alle obigen Antworten brechen das erwartete Browserverhalten. Was die meisten Leute wollen, ist etwas, das nach oben scrollt, wenn es sich um eine "neue" Seite handelt, aber zur vorherigen Position zurückkehrt, wenn Sie über die Schaltfläche "Zurück" (oder "Vorwärts") dorthin gelangen.

Wenn Sie den HTML5-Modus annehmen, stellt sich heraus, dass dies einfach ist (obwohl ich sicher bin, dass einige kluge Leute da draußen herausfinden können, wie man dies eleganter macht!):

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

Die Funktionsweise besteht darin, dass der Router davon ausgeht, dass er nach oben scrollen wird, sich jedoch etwas verzögert, um dem Browser die Möglichkeit zu geben, den Vorgang abzuschließen. Wenn der Browser uns dann benachrichtigt, dass die Änderung auf eine Vorwärts- / Rückwärtsnavigation zurückzuführen ist, wird das Zeitlimit abgebrochen und der Bildlauf wird nie ausgeführt.

Ich habe Raw- documentBefehle zum Scrollen verwendet, weil ich zum gesamten oberen Rand des Fensters wechseln möchte. Wenn Sie nur ui-viewscrollen möchten , legen autoscroll="my_var"Sie my_varmithilfe der oben beschriebenen Techniken fest, wo Sie steuern . Aber ich denke, die meisten Leute werden die gesamte Seite scrollen wollen, wenn Sie als "neu" auf die Seite gehen.

Die oben genannten Verwendungen ui-Router, obwohl Sie könnte ng-Route verwenden , anstatt durch den Austausch $routeChangeSuccessfür $stateChangeSuccess.

Dev93
quelle
Wie setzen Sie das um? Wo würden Sie es in den regulären Code für Winkel-UI-Routen wie den folgenden einfügen? 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 ... hat nicht funktioniert :-( Es tötet nur die App. .state('web', { url: '/webdev', templateUrl: 'partial-web.html', controller: function($scope) { $scope.clients = ['client1', 'client2', 'client3']; // I put it here } }) Ich lerne gerade dieses Zeug, vielleicht fehlt mir etwas: D
Agent Zebra
@AgentZebra Zumindest muss der Controller $ timeout als Eingabe verwenden (da mein Code darauf verweist), aber was vielleicht noch wichtiger ist, der Controller, den ich erwähne, muss auf einer Ebene über einem einzelnen Zustand leben (Sie) könnte die Anweisungen in Ihrem Top-Level-Controller enthalten). Die anfängliche AngularJS-Lernkurve ist in der Tat sehr schwierig; Viel Glück!
Dev93
2

Keine der Antworten löste mein Problem. Ich verwende eine Animation zwischen Ansichten und das Scrollen würde immer nach der Animation erfolgen. Die Lösung, die ich gefunden habe, damit das Scrollen nach oben vor der Animation erfolgt, ist die folgende Anweisung:

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

            })
        }
    };
}]);

Ich habe es in meiner Ansicht wie folgt eingefügt:

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

Einfache Lösung, fügen Sie scrollPositionRestorationim aktivierten Hauptroutenmodul hinzu.
So was:

const routes: Routes = [

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

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

Ich habe endlich bekommen, was ich brauchte.

Ich musste nach oben scrollen, wollte aber einige Übergänge nicht

Sie können dies auf Route-für-Route-Ebene steuern.
Ich kombiniere die obige Lösung von @wkonkel und füge eine einfache hinzunoScroll: true einigen Parameter . Dann fange ich das beim Übergang ein.

Alles in allem: Dies schwebt bei neuen Übergängen an den oberen Rand der Seite, bei Forward/ BackÜbergängen nicht an den oberen Rand und ermöglicht es Ihnen, dieses Verhalten bei Bedarf zu überschreiben.

Der Code: (vorherige Lösung plus eine zusätzliche noScroll-Option)

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

Legen Sie das in Ihren app.runBlock und spritzen Sie $state...myApp.run(function($state){...})

Wenn Sie dann nicht zum Anfang der Seite scrollen möchten, erstellen Sie eine Route wie folgt:

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

Dies funktionierte für mich einschließlich Autoscroll

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

Teja
quelle