Cambiar la ruta no se desplaza hacia arriba en la nueva página

271

He encontrado algunos comportamientos no deseados, al menos para mí, cuando cambia la ruta. En el paso 11 del tutorial http://angular.github.io/angular-phonecat/step-11/app/#/phones puede ver la lista de teléfonos. Si te desplazas hacia la parte inferior y haces clic en uno de los últimos, puedes ver que el desplazamiento no está en la parte superior, sino que está en el medio.

También encontré esto en una de mis aplicaciones y me preguntaba cómo puedo hacer que esto se desplace hacia la parte superior. Puedo hacerlo manualmente, pero creo que debería haber otra forma elegante de hacerlo que no conozco.

Entonces, ¿hay una manera elegante de desplazarse hacia la cima cuando cambia la ruta?

Matias Gonzalez
fuente

Respuestas:

578

El problema es que su ngView conserva la posición de desplazamiento cuando carga una nueva vista. Puede indicar $anchorScroll"desplazar la ventana gráfica después de actualizar la vista" (los documentos son un poco vagos, pero desplazarse aquí significa desplazarse a la parte superior de la nueva vista).

La solución es agregar autoscroll="true"a su elemento ngView:

<div class="ng-view" autoscroll="true"></div>
Konrad Kiss
fuente
1
Esto funciona para mí, la página se desplaza bien hacia arriba, pero estoy usando scrollTo con la directiva smoothScroll, ¿hay alguna manera de hacer un desplazamiento suave hacia arriba? Además, su solución se desplaza hacia la parte superior de la vista, no hacia la página para desplazarse a parte superior de la página?
xzegga
19
El estado de los documentos Si el atributo se establece sin valor, habilite el desplazamiento . Por lo tanto, puede acortar el código a solo <div class="ng-view" autoscroll></div>y funciona igual (a menos que desee que el desplazamiento sea condicional).
Daniel Liuzzi
77
no funciona para mí, el documento no dice que se desplazará hacia la parte superior
Pencilcheck
66
Me parece que si el desplazamiento automático se establece en verdadero, cuando un usuario "regresa", regresa a la parte superior de la página, no donde lo dejó, lo cual es un comportamiento no deseado.
axzr
44
esto desplazará su vista a este div. Por lo tanto, se desplazará hasta la parte superior de la página solo si está en la parte superior de la página. De lo contrario, tendrá que ejecutar $window.scrollTo(0,0)en $ stateChangeSuccess oyente de eventos
Filip Sobczak
46

Simplemente ponga este código para ejecutar

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

    window.scrollTo(0, 0);

});
xmaster
fuente
66
$window.scrollTop(0,0)funciona mejor para mí que el desplazamiento automático. En mi caso tengo vistas que entran y salen con transiciones.
IsidroGH
1
Esto también funciona mejor para mí que autoscroll = "true". El desplazamiento automático fue demasiado tarde por alguna razón, se desplazaría hacia la parte superior después de que la nueva vista ya fuera visible.
Gregory Bolkenstijn
55
Esto funcionó mejor para mí: $ rootScope. $ On ('$ stateChangeSuccess', function () {$ ("html, body"). Animate ({scrollTop: 0}, 200);});
James Gentes
$ window.scrollTop debería ser $ window.scrollTo, de lo contrario dice que no existe. ¡Esta solución funciona muy bien!
Software Prophets
@JamesGentes Yo también necesitaba este. Gracias.
Mackenzie Browne
36

Este código funcionó muy bien para mí ... Espero que también funcione bien para usted ... Todo lo que tiene que hacer es inyectar $ anchorScroll en su bloque de ejecución y aplicar la función de escucha al rootScope como lo hice en el siguiente ejemplo ...

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

Aquí está el orden de llamada del 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)

RUN BLOCK se ejecuta después de crear el inyector y se utiliza para iniciar la aplicación. Esto significa que cuando se redirige a la nueva vista de ruta, el oyente en el bloque de ejecución llama al

$ anchorScroll ()

y ahora puede ver que el desplazamiento comienza en la parte superior con la nueva vista enrutada :)

Rizwan Jamal
fuente
Finalmente, una solución que funciona con encabezados fijos. ¡Gracias!
Fabio
31

Después de una o dos horas de intentar todas las combinaciones de ui-view autoscroll=true, $stateChangeStart, $locationChangeStart, $uiViewScrollProvider.useAnchorScroll(), $provide('$uiViewScroll', ...), y muchos otros, no pude conseguir rollo-a-alto-a-new-page de trabajo como se esperaba.

Esto fue en última instancia lo que funcionó para mí. Captura pushState y replaceState y solo actualiza la posición de desplazamiento cuando se navega por nuevas páginas (el botón de retroceso / avance conserva sus posiciones de desplazamiento):

.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
fuente
Esto funciona muy bien para mi caso de uso. Estoy usando Angular UI-Router con vistas anidadas (varios niveles de profundidad). ¡Gracias!
Joshua Powell
Para su información, necesita estar en modo HTML5 en angular para usar pushState: $locationProvider.html5Mode(true); @wkronkel ¿hay alguna manera de lograr esto cuando no usa el modo HTML5 en angular? Porque la recarga de la página arroja errores 404 debido a que el modo html 5 está activo
britztopher
@britztopher Puedes conectarte a los eventos integrados y mantener tu propia historia, ¿verdad?
Casey
2
¿a dónde anexas esto .run? el angular tu appobjeto?
Philll_t
1
@Philll_t: Sí, la runfunción es un método disponible en cada objeto de módulo angular.
Hinrich
18

Aquí está mi solución (aparentemente) robusta, completa y (bastante) concisa. Utiliza el estilo compatible de minificación (y el acceso angular.module (NAME) a ​​su módulo).

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

PD: Encontré que el desplazamiento automático no tenía ningún efecto, ya sea establecido en verdadero o falso.

James
fuente
1
Hmmm ... esto desplaza la vista a la parte superior primero y luego la vista cambia en la pantalla para mí.
Strelok
Solución limpia que estaba buscando. Si hace clic en "Atrás", lo lleva a donde lo dejó, esto puede no ser deseable para algunos, pero me gusta en mi caso.
mjoyce91
15

Con angularjs UI Router, lo que estoy haciendo es esto:

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

Con:

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

Nunca falla en ninguna página, y no es global.

Léon Pelletier
fuente
1
Gran idea, puedes hacerlo más corto usando:onEnter: scrollContent
zucker
2
¿Qué pones donde escribiste // Your favorite scroll method here?
Agente Zebra
44
Buen momento: estaba dos líneas por encima de este comentario en el código original, intentando ui-router-title . Yo uso $('html, body').animate({ scrollTop: -10000 }, 100);
Léon Pelletier
1
Jaja, genial! Aunque sea extraño, no funciona para mí todo el tiempo. Tengo algunas vistas que son tan cortas que no tienen un desplazamiento, y dos vistas que tienen un desplazamiento, cuando coloco onEnter: scrollContenten cada vista solo funciona en la primera vista / ruta, no en la segunda. Extraño.
Agente Zebra
1
No estoy seguro, no, simplemente no se desplaza hacia arriba cuando debería. Cuando hago clic entre las 'rutas', algunas de ellas siguen desplazándose hacia donde está la vista ng, en lugar de la parte superior absoluta de la página adjunta. ¿Tiene sentido?
Agente Zebra
13

Para su información, para cualquiera que se encuentre con el problema descrito en el título (como lo hice yo) y que también está usando el complemento AngularUI Router ...

Como se le preguntó y respondió en esta pregunta SO, el enrutador angular-ui salta al final de la página cuando cambia las rutas.
¿No puedo entender por qué la página se carga en la parte inferior? Problema de desplazamiento automático de UI-Router angular

Sin embargo, como dice la respuesta, puede desactivar este comportamiento diciendo autoscroll="false"en su ui-view.

Por ejemplo:

<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
fuente
16
El mío no salta al fondo, solo retiene la posición de desplazamiento en lugar de ir a la parte superior.
Stephen Smith
55
Esto no responde la pregunta. Tengo el mismo problema: cuando la ruta cambia, el nivel de desplazamiento solo permanece en el mismo lugar en lugar de desplazarse hacia la parte superior. Puedo hacer que se desplace hacia la parte superior, pero solo cambiando el hash, lo que no quiero hacer por razones obvias.
Se
7

puedes usar este javascript

$anchorScroll()
CodeIsLife
fuente
3
Bueno, no es tan simple en angularjs. Su solución solo es válida en jquery. Lo que estoy buscando es una forma elegante de hacer esto en angularjs
Matias Gonzalez
44
¿Has intentado usar $ anchorScroll (), está documentado docs.angularjs.org/api/ng.%24anchorScroll .
CodeIsLife
1
FYI Si especifica una ubicación $location.hash('top')antes de que $anchorScroll()los botones predeterminados de avance / retroceso del navegador ya no funcionen; siguen redirigiéndote al hash en la URL
Roy
7

Si usa ui-router puede usar (en ejecución)

$rootScope.$on("$stateChangeSuccess", function (event, currentState, previousState) {
    $window.scrollTo(0, 0);
});
Colzak
fuente
Sé que esto debería funcionar, estoy usando ampliamente "$ stateChangeSuccess", pero por alguna razón $ window.scrollTo (0,0) no funciona con eso.
Abhas
4

Encontré esta solución. Si va a una nueva vista, la función se ejecuta.

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

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

Establecer autoScroll en verdadero no fue el truco para mí, así que elegí otra solución. Construí un servicio que se conecta cada vez que cambia la ruta y que utiliza el servicio incorporado $ anchorScroll para desplazarse hacia arriba. Funciona para mi :-).

Servicio:

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

Registro:

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

Llegué un poco tarde a la fiesta, pero probé todos los métodos posibles y nada funcionó correctamente. Aquí está mi solución elegante:

Yo uso un controlador que gobierna todas mis páginas con ui-router. Me permite redirigir a los usuarios que no están autenticados o validados a una ubicación adecuada. La mayoría de las personas ponen su middleware en la configuración de su aplicación, pero requiero algunas solicitudes http, por lo tanto, un controlador global funciona mejor para mí.

Mi index.html se ve así:

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

Mi initCtrl.js se ve así:

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

He probado todas las opciones posibles, este método funciona mejor.

BuffMcBigHuge
fuente
1
Este es el único que funcionó con material angular para mí, también id="top-nav"fue importante colocar el elemento correcto.
BatteryAcid
2

Todas las respuestas anteriores rompen el comportamiento esperado del navegador. Lo que la mayoría de la gente quiere es algo que se desplazará hacia la parte superior si se trata de una página "nueva", pero que volverá a la posición anterior si llega a través del botón Atrás (o Adelante).

Si asume el modo HTML5, esto resulta ser fácil (aunque estoy seguro de que algunas personas brillantes pueden descubrir cómo hacer que esto sea más 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;
}

La forma en que funciona es que el enrutador asume que se desplazará hacia la parte superior, pero se demora un poco para darle al navegador la oportunidad de terminar. Si el navegador nos notifica que el cambio se debió a una navegación Atrás / Adelante, cancela el tiempo de espera y el desplazamiento nunca ocurre.

Usé documentcomandos sin procesar para desplazarme porque quiero moverme a la parte superior de la ventana. Si solo desea ui-viewdesplazarse, configure autoscroll="my_var"dónde controla my_varutilizando las técnicas anteriores. Pero creo que la mayoría de la gente querrá desplazarse por toda la página si va a la página como "nueva".

Lo anterior usa ui-enrutador, aunque podría usar ng-route en su lugar intercambiando $routeChangeSuccesspor $stateChangeSuccess.

Dev93
fuente
¿Cómo implementas esto? ¿Dónde lo pondrías en el código regular de rutas angulares-ui como el siguiente? 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 ... no funcionó :-( Simplemente mata la aplicación. .state('web', { url: '/webdev', templateUrl: 'partial-web.html', controller: function($scope) { $scope.clients = ['client1', 'client2', 'client3']; // I put it here } }) Solo estoy aprendiendo estas cosas, tal vez me estoy perdiendo algo: D
Agente Zebra
@AgentZebra Como mínimo, el controlador necesitará tomar $ timeout como entrada (ya que mi código lo hace referencia), pero quizás lo más importante, el controlador que menciono necesita vivir en un nivel superior a un estado individual (usted podría incluir las declaraciones en su controlador de nivel superior). La curva de aprendizaje inicial de AngularJS es realmente muy difícil; ¡buena suerte!
Dev93
2

Ninguna de las respuestas proporcionadas resolvió mi problema. Estoy usando una animación entre vistas y el desplazamiento siempre sucedería después de la animación. La solución que encontré para que el desplazamiento hacia la parte superior suceda antes de la animación es la siguiente directiva:

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

            })
        }
    };
}]);

Lo inserté en mi vista de la siguiente manera:

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

Solución simple, agregue scrollPositionRestorationel módulo de ruta principal habilitado.
Me gusta esto:

const routes: Routes = [

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

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

Finalmente obtuve lo que necesitaba.

Necesitaba desplazarme hacia la parte superior, pero quería algunas transiciones para no

Puede controlar esto en un nivel de ruta por ruta.
Estoy combinando la solución anterior por @wkonkel y agregando un noScroll: trueparámetro simple a algunas declaraciones de ruta. Entonces estoy captando eso en la transición.

En resumen: Este flota en la parte superior de la página en nuevas transiciones, que no flote a la parte superior de Forward/ Backtransiciones, y se le permite invalidar este comportamiento si es necesario.

El código: (solución anterior más una opción adicional 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');

Pon eso en tu app.runbloque e inyecta $state...myApp.run(function($state){...})

Luego, si no desea desplazarse hasta la parte superior de la página, cree una ruta como esta:

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

Esto funcionó para mí, incluido el desplazamiento automático

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

Teja
fuente