Zmiana trasy nie przewija do góry na nowej stronie

271

Znalazłem jakieś niepożądane, przynajmniej dla mnie, zachowanie podczas zmiany trasy. W kroku 11 samouczka http://angular.github.io/angular-phonecat/step-11/app/#/phones możesz zobaczyć listę telefonów. Jeśli przewiniesz na dół i klikniesz jeden z najnowszych, zobaczysz, że zwoju nie ma na górze, a raczej w środku.

Znalazłem to również w jednej z moich aplikacji i zastanawiałem się, jak mogę to przewinąć do góry. Mogę to zrobić ręcznie, ale uważam, że powinien istnieć inny elegancki sposób, którego nie znam.

Czy istnieje elegancki sposób przewijania na górę, gdy zmienia się trasa?

Matias Gonzalez
źródło

Odpowiedzi:

578

Problem polega na tym, że twój ngView zachowuje pozycję przewijania podczas ładowania nowego widoku. Możesz poinstruować, $anchorScrollaby „przewinąć rzutnię po zaktualizowaniu widoku” ( dokumenty są nieco niejasne, ale przewijanie tutaj oznacza przewijanie do góry nowego widoku).

Rozwiązaniem jest dodanie autoscroll="true"do elementu ngView:

<div class="ng-view" autoscroll="true"></div>
Konrad Kiss
źródło
1
To dla mnie działa, przewijanie strony w porządku do góry, ale używam scrollTo z dyrektywą smoothScroll, czy istnieje sposób, aby zrobić płynne przewijanie do góry ?, dodatkowo rozwiązanie przewiń do góry, a nie do strony, aby przewinąć do Góra strony?
xzegga,
19
Stan dokumentów Jeśli atrybut jest ustawiony bez wartości, włącz przewijanie . Możesz więc skrócić kod do just <div class="ng-view" autoscroll></div>i działa on tak samo (chyba że chcesz uzależnić przewijanie).
Daniel Liuzzi,
7
to nie działa dla mnie, dokument nie mówi, że przewinie się do góry
Pencilcheck 13.01.2015
6
Uważam, że jeśli automatyczne przewijanie jest ustawione na true, to kiedy użytkownik „wraca”, wraca na górę strony, a nie tam, gdzie ją zostawił, co jest niepożądanym zachowaniem.
axzr
4
spowoduje to przewinięcie widoku do tego div. Przewinie to do góry strony tylko wtedy, gdy znajdzie się na górze strony. W przeciwnym razie będziesz musiał uruchomić się $window.scrollTo(0,0)w nasłuchiwaniu zdarzeń $ stateChangeSuccess
Filip Sobczak
46

Po prostu uruchom ten kod

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

    window.scrollTo(0, 0);

});
xmaster
źródło
6
$window.scrollTop(0,0)działa dla mnie lepiej niż automatyczne przewijanie. W moim przypadku mam widoki, które wchodzą i wychodzą z przejściami.
IsidroGH
1
Działa to również dla mnie lepiej niż autoscroll = "true". Automatyczne przewijanie z jakiegoś powodu było za późno, przewijałoby się do góry, gdy nowy widok był już widoczny.
Gregory Bolkenstijn
5
Działa to dla mnie najlepiej: $ rootScope. $ On ('$ stateChangeSuccess', function () {$ ("html, body"). Animacja ({scrollTop: 0}, 200);});
James Gentes,
$ window.scrollTop powinien być $ window.scrollTo, w przeciwnym razie mówi, że nie istnieje. To rozwiązanie działa świetnie!
Software Prophets,
@JamesGentes też tego potrzebowałem. Dzięki.
Mackenzie Browne
36

Ten kod działał świetnie dla mnie .. Mam nadzieję, że będzie również działał świetnie dla Ciebie .. Wszystko, co musisz zrobić, to po prostu wstrzyknąć $ anchorScroll do bloku uruchamiania i zastosować funkcję detektora do rootScope, jak to zrobiłem w poniższym przykładzie ..

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

Oto kolejność wywoływania modułu 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 jest wykonywany po utworzeniu wtryskiwacza i służy do uruchomienia aplikacji. Oznacza to, że po przekierowaniu do nowego widoku trasy słuchacz w bloku uruchamiania wywołuje metodę

$ anchorScroll ()

i możesz teraz zobaczyć, że przewijanie zaczyna się na górze z nowym widokiem trasy :)

Rizwan Jamal
źródło
Wreszcie rozwiązanie działające z lepkimi nagłówkami! Dziękuję Ci!
Fabio
31

Po godzinie lub dwóch prób każdą kombinację ui-view autoscroll=true, $stateChangeStart, $locationChangeStart, $uiViewScrollProvider.useAnchorScroll(), $provide('$uiViewScroll', ...), i wiele innych, nie mogłem dostać zwój-to-top-on-nowej stronie, aby działać zgodnie z oczekiwaniami.

To ostatecznie działało dla mnie. Przechwytuje pushState i replaceState i aktualizuje pozycję przewijania tylko po przejściu do nowych stron (przycisk Wstecz / Dalej zachowuje pozycje przewijania):

.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
źródło
Działa to świetnie w moim przypadku użycia. Używam Angular UI-Routera z widokami zagnieżdżonymi (kilka poziomów głębokości). Dzięki!
Joshua Powell,
Do Twojej wiadomości - musisz być w trybie HTML5 w wersji kątowej, aby użyć pushState: $locationProvider.html5Mode(true); @wkronkel jest sposób, aby to zrobić, gdy nie używasz trybu HTML5 w wersji kątowej?
Ponowne ładowanie strony generuje
@britztopher Możesz dołączyć do wbudowanych wydarzeń i prowadzić własną historię, prawda?
Casey
2
do czego to dołączasz .run? kątowy twój appobiekt?
Philll_t
1
@Philll_t: Tak, runfunkcja jest metodą dostępną na każdym obiekcie modułu kątowego.
Hinrich
18

Oto moje (pozornie) solidne, kompletne i (dość) zwięzłe rozwiązanie. Wykorzystuje styl zgodny z minimalizacją (i dostęp do modułu angular.module (NAME)).

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

PS Odkryłem, że funkcja automatycznego przewijania nie miała żadnego wpływu, czy jest ustawiona na prawda czy fałsz.

James
źródło
1
Hmmm .. to najpierw przewija widok do góry, a potem widok zmienia się dla mnie.
Strelok
Czyste rozwiązanie, którego szukałem. Jeśli klikniesz „wstecz”, zabierze Cię do miejsca, w którym przerwałeś - dla niektórych może to nie być pożądane, ale w moim przypadku podoba mi się to.
mjoyce91
15

Używając routera interfejsu użytkownika angularjs, robię to:

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

Z:

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

Nigdy nie zawodzi na żadnej stronie i nie jest globalny.

Léon Pelletier
źródło
1
Świetny pomysł, możesz go skrócić, używając:onEnter: scrollContent
zucker
2
Co umieściłeś tam, gdzie napisałeś // Your favorite scroll method here?
Agentka Zebra,
4
Dobry czas: byłem o dwie linie powyżej tego komentarza w oryginalnym kodzie, próbując ui-router-title . Używam $('html, body').animate({ scrollTop: -10000 }, 100);
Léon Pelletier,
1
Haha, świetnie! Dziwne, nie działa mi to cały czas. Mam kilka widoków, które są tak krótkie, że nie mają zwoju, i dwa widoki z przewijaniem, kiedy umieszczam onEnter: scrollContentna każdym widoku, działa tylko na pierwszym widoku / trasie, a nie na drugim. Dziwne.
Agentka Zebra,
1
Nie jestem pewien, nie, to po prostu nie przewija do góry, kiedy powinno. Kiedy klikam między „trasami”, niektóre z nich nadal przewijają do miejsca, w którym znajduje się widok ng, a nie do absolutnego początku otaczającej strony. Czy to ma sens?
Agentka Zebra,
13

Informacje dla każdego, kto napotka problem opisany w tytule (tak jak ja), który również korzysta z wtyczki AngularUI Router ...

Zgodnie z pytaniem i odpowiedzią na to pytanie SO router angular-ui przeskakuje na dół strony podczas zmiany tras.
Nie możesz dowiedzieć się, dlaczego strona ładuje się na dole? Problem automatycznego przewijania Angular UI-Router

Jednak, jak mówi odpowiedź, możesz wyłączyć to zachowanie, mówiąc autoscroll="false"na swoim ui-view.

Na przykład:

<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
źródło
16
Mój nie zeskakuje na dół, po prostu zachowuje pozycję przewijania zamiast wchodzić na górę.
Stephen Smith
5
To nie odpowiada na pytanie. Mam ten sam problem - gdy zmienia się trasa, poziom przewijania pozostaje w tym samym miejscu zamiast przewijania w górę. Mogę go przewinąć na górę, ale tylko przez zmianę skrótu, czego nie chcę robić z oczywistych powodów.
Czy
7

możesz użyć tego javascript

$anchorScroll()
CodeIsLife
źródło
3
Cóż, nie jest to takie proste w angularjs. Twoje rozwiązanie jest ważne tylko w jquery. To, czego szukam, to elegancki sposób na zrobienie tego w angularjs
Matias Gonzalez
4
Jeśli próbowałeś użyć $ anchorScroll (), jest to udokumentowane docs.quarejs.org/api/ng.%24anchorScroll .
CodeIsLife,
1
FYI Jeśli określisz lokalizację $location.hash('top')przed $anchorScroll()domyślnymi przyciskami przeglądarki do przodu / do tyłu, przestaną działać; przekierowują cię do skrótu w adresie URL
Roy
7

Jeśli używasz interfejsu użytkownika routera, możesz go używać (podczas uruchamiania)

$rootScope.$on("$stateChangeSuccess", function (event, currentState, previousState) {
    $window.scrollTo(0, 0);
});
Colzak
źródło
Wiem, że to powinno działać, intensywnie używam „$ stateChangeSuccess”, ale z jakiegoś powodu $ window.scrollTo (0,0) nie działa z tym.
Abhas
4

Znalazłem to rozwiązanie. Jeśli przejdziesz do nowego widoku, funkcja zostanie wykonana.

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

    app.controller('indexController', function ($scope, $window) {
        $scope.$on('$viewContentLoaded', function () {
            $window.scrollTo(0, 0);
        });
    });
Vince Verhoeven
źródło
3

Ustawienie autoScroll na wartość true nie było dla mnie problemem, więc wybrałem inne rozwiązanie. Zbudowałem usługę, która zaczepia się za każdym razem, gdy zmienia się trasa i która korzysta z wbudowanej usługi $ anchorScroll do przewijania na górę. Pracuje dla mnie :-).

Usługa:

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

Rejestracja:

angular.module("mymodule").run(["pageSwitch", function (pageSwitch) {
    pageSwitch.registerListener();
}]);
asp_net
źródło
3

Trochę późno na imprezę, ale wypróbowałem każdą możliwą metodę i nic nie działało poprawnie. Oto moje eleganckie rozwiązanie:

Używam kontrolera, który zarządza wszystkimi moimi stronami za pomocą interfejsu użytkownika. Pozwala mi przekierowywać użytkowników, którzy nie są uwierzytelnieni lub zatwierdzeni do odpowiedniej lokalizacji. Większość ludzi umieszcza swoje oprogramowanie pośrednie w konfiguracji aplikacji, ale wymagałem kilku żądań HTTP, dlatego globalny kontroler działa dla mnie lepiej.

Mój index.html wygląda następująco:

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

Mój initCtrl.js wygląda następująco:

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

Wypróbowałem każdą możliwą opcję, ta metoda działa najlepiej.

BuffMcBigHuge
źródło
1
To jedyny, który pracował dla mnie z materiałem kątowym, id="top-nav"ważne było również umieszczenie odpowiedniego elementu.
BatteryAcid,
2

Wszystkie powyższe odpowiedzi psują oczekiwane zachowanie przeglądarki. Większość ludzi chce czegoś, co przewinie na górę, jeśli jest to „nowa” strona, ale wróci do poprzedniej pozycji, jeśli przejdziesz tam za pomocą przycisku Wstecz (lub Dalej).

Jeśli przyjmiesz tryb HTML5, okaże się to łatwe (chociaż jestem pewien, że niektórzy bystrzy ludzie mogą dowiedzieć się, jak uczynić to bardziej eleganckim!):

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

Działa to tak, że router zakłada, że ​​przewinie się do góry, ale nieco opóźnia, aby dać przeglądarce szansę na zakończenie. Jeśli przeglądarka powiadomi nas wtedy, że zmiana była spowodowana nawigacją wstecz / do przodu, anuluje limit czasu i przewijanie nigdy nie nastąpi.

Użyłem surowych documentpoleceń do przewijania, ponieważ chcę przenieść się na całą górę okna. Jeśli chcesz tylko ui-viewprzewijać, ustaw miejsce, w autoscroll="my_var"którym kontrolujesz, my_varkorzystając z powyższych technik. Ale myślę, że większość ludzi będzie chciała przewinąć całą stronę, jeśli przejdziesz na stronę jako „nową”.

Powyższych zastosowań UI-routera, choć można użyć NG-trasę zamiast poprzez zamianę $routeChangeSuccessna $stateChangeSuccess.

Dev93
źródło
Jak to wdrożyć? Gdzie umieścisz go w zwykłym kodzie tras kątowych interfejsu użytkownika, takim jak poniżej? 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' }) });
Agentka Zebra,
hmmm ... nie działało :-( To po prostu zabija aplikację. .state('web', { url: '/webdev', templateUrl: 'partial-web.html', controller: function($scope) { $scope.clients = ['client1', 'client2', 'client3']; // I put it here } }) Właśnie się tego uczę, może coś mi umknęło: D
Agent Zebra
@AgentZebra Jako minimum, kontroler będzie musiał wprowadzić $ timeout jako dane wejściowe (ponieważ mój kod go odwołuje), ale być może, co ważniejsze, kontroler, o którym mówię, musi żyć na poziomie powyżej indywidualnego stanu (ty może zawierać instrukcje w kontrolerze najwyższego poziomu). Początkowa krzywa uczenia się AngularJS jest rzeczywiście bardzo trudna; powodzenia!
Dev93
2

Żadna z podanych odpowiedzi nie rozwiązała mojego problemu. Używam animacji między widokami, a przewijanie zawsze odbywa się po animacji. Rozwiązaniem, które znalazłem, aby przewinięcie do góry nastąpiło przed animacją, jest następująca dyrektywa:

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

            })
        }
    };
}]);

Wstawiłem go do mojego widoku w następujący sposób:

<div scroll-to-top-before-animation>
    <div ng-view class="view-animation"></div>
</div>
aBertrand
źródło
2

Proste rozwiązanie, dodaj scrollPositionRestorationwłączony główny moduł trasy.
Lubię to:

const routes: Routes = [

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

 @NgModule({
  imports: [
   RouterModule.forRoot(routes,{scrollPositionRestoration:'enabled'})
  ],
 exports: [
 RouterModule
 ]
 })
  export class AppRoutingModule { }
Farida Anjum
źródło
0

W końcu dostałem to, czego potrzebowałem.

Musiałem przewinąć na górę, ale chciałem, żeby niektóre przejścia tego nie zrobiły

Możesz to kontrolować na poziomie trasy po trasie.
Łączę powyższe rozwiązanie przez @wkonkel i dodam prosty noScroll: trueparametr do niektórych deklaracji trasy. Potem łapię to w trakcie przejścia.

Podsumowując: To unosi się na górę strony przy nowych przejściach, nie unosi się na górę na Forward/ Backprzejściach i pozwala zastąpić to zachowanie, jeśli to konieczne.

Kod: (poprzednie rozwiązanie plus dodatkowa opcja noScroll)

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

Włóż to do swojego app.runbloku i wstrzyknij $state...myApp.run(function($state){...})

Następnie, jeśli nie chcesz przewijać do góry strony, utwórz trasę taką jak ta:

.state('someState', {
  parent: 'someParent',
  url: 'someUrl',
  noScroll : true // Notice this parameter here!
})
Augie Gardner
źródło
0

To działało dla mnie, w tym automatyczne przewijanie

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

Teja
źródło