Mudar de rota não rola para o topo na nova página

Eu encontrei algum comportamento indesejado, pelo menos para mim, quando a rota muda. No passo 11 do tutorial http://angular.github.io/angular-phonecat/step-11/app/#/phones você pode ver a lista de telefones. Se você rolar até a parte inferior e clicar em uma das últimas, verá que a rolagem não está no topo, ao contrário, está no meio.

Eu encontrei isso em um dos meus aplicativos também e fiquei me perguntando como posso fazer isso rolar para o topo. Eu posso fazer isso mannually, mas acho que deveria haver outra maneira elegante de fazer isso que eu não sei.

Então, há uma maneira elegante de rolar para o topo quando a rota muda?

O problema é que seu ngView mantém a posição de rolagem quando carrega uma nova visão. Você pode instruir $anchorScroll para “rolar a viewport após a atualização da exibição” (os documentos são um pouco vagos, mas rolar aqui significa rolar para o topo da nova exibição).

A solução é adicionar autoscroll="true" ao seu elemento ngView:

 

Basta colocar esse código para rodar

 $rootScope.$on("$routeChangeSuccess", function (event, currentRoute, previousRoute) { window.scrollTo(0, 0); }); 

Este código funcionou muito bem para mim … Espero que também funcione muito bem para você. Tudo o que você precisa fazer é injetar $ anchorScroll no seu bloco de execução e aplicar a function listener ao rootScope, como eu fiz no exemplo abaixo.

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

Aqui está a ordem de chamada do módulo 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)

O BLOCO DE EXECUÇÃO é executado depois que o injetor é criado e usado para executar o kickstart do aplicativo. Isso significa que, quando você redireciona para a nova visualização de rota, o ouvinte no bloco de execução chama o

$ anchorScroll ()

e agora você pode ver a rolagem começar no topo com a nova visualização roteada 🙂

Depois de uma ou duas horas experimentando cada combinação de ui-view autoscroll=true , $stateChangeStart , $locationChangeStart , $uiViewScrollProvider.useAnchorScroll() , $provide('$uiViewScroll', ...) , e muitos outros, eu não pude t rolar para o topo na nova página para funcionar como esperado.

Isso foi basicamente o que funcionou para mim. Ele captura pushState e replaceState e apenas atualiza a posição de rolagem quando novas páginas são navegadas para (o botão voltar / avançar mantém suas posições de rolagem):

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

Aqui está minha solução (aparentemente) robusta, completa e (razoavelmente) concisa. Ele usa o estilo compatível com minification (e o access angular.module (NAME) ao seu módulo).

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

PS Eu achei que a coisa autoscroll não teve efeito se definido como verdadeiro ou falso.

Usando o angularjs UI Router, o que estou fazendo é isto:

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

Com:

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

Nunca falha em nenhuma página e não é global.

FYI para para quem vem através do problema descrito no título (como eu fiz), que também está usando o plugin AngularUI Router

Conforme solicitado e respondido nesta pergunta SO, o roteador angular-ui salta para a parte inferior da página quando você muda de rota.
Não consegue descobrir por que a página é carregada na parte inferior? Problema de desvio automático de roteador de interface do usuário angular

No entanto, como a resposta afirma, você pode desativar esse comportamento dizendo autoscroll="false" em sua ui-view .

Por exemplo:

 

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

você pode usar este javascript

 $anchorScroll() 

Se você usa o roteador-ui, você pode usar (em execução)

 $rootScope.$on("$stateChangeSuccess", function (event, currentState, previousState) { $window.scrollTo(0, 0); }); 

Eu encontrei esta solução. Se você for para uma nova visão, a function será executada.

 var app = angular.module('hoofdModule', ['ngRoute']); app.controller('indexController', function ($scope, $window) { $scope.$on('$viewContentLoaded', function () { $window.scrollTo(0, 0); }); }); 

Definir autoScroll como true não foi o truque para mim, então escolhi outra solução. Eu construí um serviço que engancha em cada vez que a rota muda e que usa o serviço built-in $ anchorScroll para rolar para cima. Funciona para mim :-).

Serviço:

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

Cadastro:

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

Todas as respostas acima violam o comportamento esperado do navegador. O que a maioria das pessoas deseja é algo que rolará para o topo se for uma página “nova”, mas retornará à posição anterior se você estiver chegando lá através do botão Voltar (ou Avançar).

Se você assumir o modo HTML5, isso acaba sendo fácil (embora eu tenha certeza de que algumas pessoas shinys possam descobrir como tornar isso mais elegante!):

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

A maneira como funciona é que o roteador presume que vai rolar até o topo, mas atrasa um pouco para dar ao navegador uma chance de terminar. Se o navegador nos notificar que a alteração ocorreu devido a uma navegação Voltar / Avançar, ele cancelará o tempo limite e a rolagem nunca ocorrerá.

Eu usei comandos de document brutos para rolar, porque eu quero passar para todo o topo da janela. Se você quer apenas que sua ui-view role, então defina autoscroll="my_var" onde você controla my_var usando as técnicas acima. Mas eu acho que a maioria das pessoas vai querer rolar a página inteira se você estiver indo para a página como “novo”.

O acima usa o ui-router, embora você possa usar ng-route ao invés de trocar $routeChangeSuccess por $stateChangeSuccess .

Um pouco atrasado para a festa, mas eu tentei todos os methods possíveis, e nada funcionou corretamente. Aqui está a minha solução elegante:

Eu uso um controlador que governa todas as minhas páginas com o roteador-ui. Isso me permite redirect os usuários que não são autenticados ou validados para um local apropriado. A maioria das pessoas coloca seu middleware na configuração do seu aplicativo, mas eu precisei de algumas solicitações de http, portanto, um controlador global funciona melhor para mim.

Meu index.html se parece com:

 

Meu initCtrl.js se parece com:

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

Eu tentei todas as opções possíveis, esse método funciona melhor.

Nenhuma das respostas fornecidas resolveu o meu problema. Eu estou usando uma animação entre vistas e a rolagem sempre aconteceria depois da animação. A solução que encontrei para que a rolagem para o topo acontecesse antes da animação ser a seguinte diretiva:

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

Eu inseri na minha opinião da seguinte forma:

 

Experimente este http://ionicframework.com/docs/api/service/ $ ionicScrollDelegate /

Ele rola para o topo da lista scrollTop ()

Eu finalmente consegui o que eu precisava.

Eu precisava rolar para o topo, mas queria algumas transições para não

Você pode controlar isso em um nível de rota por rota.
Estou combinando a solução acima por @wkonkel e adicionando um simples parâmetro noScroll: true a algumas declarações de rota. Então eu estou pegando isso na transição.

Ao todo: Isso flutua no topo da página em novas transições, não flutua até o topo nas transições Forward / Back , e permite que você sobrescreva esse comportamento se necessário.

O código: (solução anterior mais uma opção extra 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'); 

Coloque isso no seu bloco app.run e injete $statemyApp.run(function($state){...})

Então, se você não quiser rolar para o topo da página, crie uma rota como esta:

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

Isso funcionou para mim, incluindo autoscroll