Alterar rota não rola para o topo da nova página

271

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

Eu encontrei isso em um dos meus aplicativos também e fiquei pensando como posso fazer isso rolar para o topo. Posso fazer isso manualmente, mas acho que deve haver outra maneira elegante de fazer isso que eu não conheço.

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

Matias Gonzalez
fonte

Respostas:

578

O problema é que o ngView mantém a posição de rolagem quando carrega uma nova exibição. Você pode instruir $anchorScrolla "rolar a janela de exibição depois que a exibição for atualizada" (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:

<div class="ng-view" autoscroll="true"></div>
Konrad Kiss
fonte
1
Este trabalho para mim, a rolagem da página é fina até o topo, mas eu estou usando scrollTo com a diretiva smoothScroll, existe uma maneira de fazer uma rolagem smoth para o topo? Inicio da página?
xzegga
19
O estado dos documentos Se o atributo estiver definido sem valor, ative a rolagem . Portanto, você pode reduzir o código para apenas <div class="ng-view" autoscroll></div>e ele funciona da mesma forma (a menos que você queira tornar a rolagem condicional).
Daniel Liuzzi
7
ele não funciona para mim, o doc não diz que vai rolar até o topo
Pencilcheck
6
Acho que, se a rolagem automática estiver definida como true, quando um usuário 'voltar', ele retornará ao topo da página, não onde o deixou, o que é um comportamento indesejado.
axzr
4
isso rolará sua visão para esta div. Portanto, ele irá rolar para o topo da página apenas se estiver no topo da página. Caso contrário, você terá que executar o $window.scrollTo(0,0)ouvinte de evento $ stateChangeSuccess
Filip Sobczak
46

Basta colocar esse código para executar

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

    window.scrollTo(0, 0);

});
xmaster
fonte
6
$window.scrollTop(0,0)funciona melhor para mim do que a rolagem automática. No meu caso, tenho visualizações que entram e saem com transições.
IsidroGH
1
Isso também funciona melhor para mim do que autoscroll = "true". A rolagem automática era muito tarde, por algum motivo, rolaria para o topo depois que a nova exibição já estivesse visível.
Gregory Bolkenstijn
5
Isso funcionou melhor para mim: $ rootScope. $ On ('$ stateChangeSuccess', function () {$ ("html, body"). Animate ({scrollTop: 0}, 200);});
James Gentes
$ window.scrollTop deve ser $ window.scrollTo, caso contrário, ele diz que não existe. Esta solução funciona muito bem!
Profetas de Software
@JamesGentes eu precisava disso também. Obrigado.
Mackenzie Browne
36

Esse código funcionou muito bem para mim .. Espero que também funcione bem para você .. Tudo o que você precisa fazer é injetar $ anchorScroll no seu bloco de execução e aplicar a função listener ao rootScope, como 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 RUN BLOCK é executado depois que o injetor é criado e é usado para iniciar o aplicativo. Significa que quando você redireciona para a nova exibição de rota, o ouvinte no bloco de execução chama o

$ anchorScroll ()

e agora você pode ver o rolo começar no topo com a nova exibição roteada :)

Rizwan Jamal
fonte
Finalmente, uma solução que está trabalhando com cabeçalhos colados! Obrigado!
Fabio
31

Depois de uma ou duas horas de tentar todas as combinações de ui-view autoscroll=true, $stateChangeStart, $locationChangeStart, $uiViewScrollProvider.useAnchorScroll(), $provide('$uiViewScroll', ...), e muitos outros, eu não poderia começar de rolagem-to-top-on-new-page a funcionar como esperado.

Isso foi o que funcionou para mim. Ele captura pushState e replaceState e atualiza apenas 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');
})
wkonkel
fonte
Isso funciona muito bem para o meu caso de uso. Estou usando o Angular UI-Router com visualizações aninhadas (vários níveis de profundidade). Obrigado!
Joshua Powell
FYI - você precisa estar no modo HTML5 em ângulo para usar pushState: $locationProvider.html5Mode(true); @wkronkel existe uma maneira de fazer isso quando não estiver usando o modo HTML5 em ângulo? Porque recarregar a página está jogando 404 erros devido a html 5 modo de ser ativo
britztopher
@britztopher Você pode se conectar aos eventos internos e manter sua própria história, certo?
Casey
2
onde você anexa isso .run? o angular seu appobjeto?
Philll_t 16/02
1
@Philll_t: Sim, a runfunção é um método disponível em todos os objetos de módulos angulares.
Hinrich 15/05
18

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

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

PS: Descobri que a coisa de rolagem automática não tinha efeito, seja configurada como verdadeira ou falsa.

James
fonte
1
Hmmm .. isso rola a vista para o topo primeiro e, em seguida, a exibição muda na tela para mim.
Strelok 02/02
Solução limpa que eu estava procurando. Se você clicar em "voltar", você será levado para onde parou - isso pode não ser desejável para alguns, mas eu gosto no meu caso.
precisa saber é o seguinte
15

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

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

Com:

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

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

Léon Pelletier
fonte
1
Ótima idéia, você pode torná-lo mais curto usando:onEnter: scrollContent
zucker 8/15
2
O que você coloca onde escreveu // Your favorite scroll method here?
Agent Zebra
4
Bom momento: eu estava duas linhas acima deste comentário no código original, tentando ui-router-title . Eu uso $('html, body').animate({ scrollTop: -10000 }, 100);
Léon Pelletier
1
Haha, ótimo! Estranho, não funciona para mim o tempo todo. Tenho algumas visualizações que são tão curtas que não têm um pergaminho e duas vistas que têm um pergaminho, quando coloco onEnter: scrollContentem cada vista que funciona apenas na primeira vista / rota, e não na segunda. Estranho.
Agente Zebra
1
Não tenho certeza, não, apenas não rola para o topo quando deveria. Quando clico entre as 'rotas', algumas delas ainda estão rolando para onde está a ng-view, em vez da parte superior absoluta da página anexa. Isso faz sentido?
Agent Zebra
13

FYI para quem se deparar com o problema descrito no título (como eu fiz) que também está usando o plug- in do AngularUI Router ...

Conforme solicitado e respondido nesta pergunta de SO, o roteador angular-ui pula para a parte inferior da página quando você altera as rotas.
Não consegue descobrir por que a página é carregada na parte inferior? Problema de rolagem automática angular do roteador da interface do usuário

No entanto, como a resposta indica, você pode desativar esse comportamento dizendo o autoscroll="false"seu ui-view.

Por exemplo:

<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
fonte
16
O meu não pula para o fundo, apenas mantém a posição de rolagem em vez de ir para o topo.
9788 Stephen
5
Isso não responde à pergunta. Eu tenho o mesmo problema - quando a rota muda, o nível de rolagem permanece no mesmo lugar, em vez de rolar para o topo. Posso fazer com que ele role até o topo, mas apenas alterando o hash, o que não quero fazer por razões óbvias.
Will
7

você pode usar esse javascript

$anchorScroll()
CodeIsLife
fonte
3
Bem, não é tão simples em angularjs. Sua solução é válida apenas no jquery. O que eu estou procurando é um elegante maneira de fazer isso em AngularJS
Matias Gonzalez
4
Você já tentou usar $ anchorScroll (), está documentado em docs.angularjs.org/api/ng.%24anchorScroll .
CodeIsLife
1
Para sua informação, se você especificar um local $location.hash('top')antes $anchorScroll()dos botões padrão de avançar / voltar do navegador, não funcionará mais; eles continuam redirecionando você para o hash na URL
Roy
7

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

$rootScope.$on("$stateChangeSuccess", function (event, currentState, previousState) {
    $window.scrollTo(0, 0);
});
Colzak
fonte
Eu sei que isso deve funcionar, estou usando extensivamente "$ stateChangeSuccess", mas por algum motivo $ window.scrollTo (0,0) não está funcionando com isso.
Abhas 04/11/2015
4

Encontrei esta solução. Se você for para uma nova visualização, a função será executada.

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

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

Definir autoScroll como true não foi o truque para mim, então escolhi outra solução. Criei um serviço que liga sempre que a rota muda e que usa o serviço $ anchorScroll interno para rolar para o topo. 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();
}]);
asp_net
fonte
3

Um pouco atrasado para a festa, mas eu tentei todos os métodos 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 ui-router. Ele me permite redirecionar usuários que não são autenticados ou validados para um local apropriado. A maioria das pessoas coloca o middleware na configuração do aplicativo, mas eu solicitei algumas solicitações http, portanto, um controlador global funciona melhor para mim.

Meu index.html se parece com:

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

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.

BuffMcBigHuge
fonte
1
Este é o único que trabalhou com material angular para mim, também id="top-nav"foi importante colocar o elemento correto.
BatteryAcid
2

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

Se você assumir o modo HTML5, isso será fácil (embora eu tenha certeza de que algumas pessoas brilhantes podem 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 assume que vai rolar para o topo, mas demora um pouco para dar ao navegador a chance de terminar. Se o navegador nos notificar que a alteração ocorreu devido a uma navegação Retroceder / Avançar, cancelará o tempo limite e a rolagem nunca ocorrerá.

Eu usei documentcomandos brutos para rolar, porque quero ir para o topo inteiro da janela. Se você quiser apenas ui-viewrolar, defina autoscroll="my_var"onde você controla my_varusando as técnicas acima. Mas acho que a maioria das pessoas vai querer rolar a página inteira se você estiver indo para a página como "nova".

O exemplo acima usa o ui-router, embora você possa usar o ng-route trocando $routeChangeSuccesspor $stateChangeSuccess.

Dev93
fonte
Como você implementa isso? Onde você o colocaria no código de rotas angular-ui regulares, como o seguinte? 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' }) });
Agente Zebra
hmmm ... não funcionou :-( Ele só mata o aplicativo. .state('web', { url: '/webdev', templateUrl: 'partial-web.html', controller: function($scope) { $scope.clients = ['client1', 'client2', 'client3']; // I put it here } }) Estou aprendendo essas coisas, talvez eu estou faltando alguma coisa: D
Agente Zebra
@AgentZebra No mínimo, o controlador precisará usar $ timeout como entrada (já que meu código faz referência a ele), mas talvez mais importante, o controlador que estou mencionando precisa viver em um nível acima de um estado individual (você pode incluir as instruções no seu controlador de nível superior). A curva de aprendizado inicial do AngularJS é realmente muito difícil; boa sorte!
Dev93
2

Nenhuma das respostas fornecidas resolveu meu problema. Estou usando uma animação entre modos de exibição e a rolagem sempre acontece após a animação. A solução que encontrei para que a rolagem para o topo aconteça antes da animação é 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 o inseri no meu modo de exibição da seguinte maneira:

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

Solução simples, adicione scrollPositionRestorationo módulo de rota principal ativado.
Como isso:

const routes: Routes = [

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

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

Finalmente consegui o que 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 com @wkonkel e adicionando uma simplesnoScroll: true parâmetro a algumas declarações de rota. Então eu estou vendo isso na transição.

Em suma: Isso flutua no topo da página em novas transições, não flutua no topo em Forward/ Backtransitions e permite que você substitua esse comportamento, se necessário.

O código: (solução anterior mais uma opção noScroll extra)

  // 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 app.runbloco e injete$state ...myApp.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!
})
Augie Gardner
fonte
0

Isso funcionou para mim, incluindo rolagem automática

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

Teja
fonte