ChatGPT解决这个技术问题 Extra ChatGPT

Changing route doesn't scroll to top in the new page

I've found some undesired, at least for me, behaviour when the route changes. In the step 11 of the tutorial http://angular.github.io/angular-phonecat/step-11/app/#/phones you can see the list of phones. If you scroll to the bottom and click on one of the latest, you can see that the scroll isn't at top, instead is kind of in the middle.

I've found this in one of my apps too and I was wondering how can I get this to scroll to the top. I can do it mannually, but I think that there should be other elegant way to do this which I don't know.

So, is there an elegant way to scroll to the top when the route changes?


K
Konrad Kiss

The problem is that your ngView retains the scroll position when it loads a new view. You can instruct $anchorScroll to "scroll the viewport after the view is updated" (the docs are a bit vague, but scrolling here means scrolling to the top of the new view).

The solution is to add autoscroll="true" to your ngView element:

<div class="ng-view" autoscroll="true"></div>

This work for me, the page scroll fine to top, but I am using scrollTo with smoothScroll directive, are there way to do a smoth scroll to top?, additionally your solution scroll to top of view not to the page are there to scroll to top of the page?
The docs state If the attribute is set without value, enable scrolling. So you can shorten the code to just <div class="ng-view" autoscroll></div> and it works just the same (unless you wanted to make scrolling conditional).
it doesn't work for me, the doc doesn't say it will scroll to the top
I find that if autoscroll is set to true then when a user 'goes back' they return to the top of the page, not where they left it, which is undesired behaviour.
this will scroll your view to this div. So it will scroll to top of the page only if it happens to be at the top of the page. Otherwise you'll have to run $window.scrollTo(0,0) in $stateChangeSuccess event listener
r
rink.attendant.6

Just put this code to run

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

    window.scrollTo(0, 0);

});

$window.scrollTop(0,0)works better for me than autoscroll. In my case I have views that enter and leave with transitions.
This also works better for me than autoscroll="true". Autoscroll was too late for some reason, it would scroll to the top after the new view was already visible.
This worked best for me: $rootScope.$on('$stateChangeSuccess',function(){ $("html, body").animate({ scrollTop: 0 }, 200); });
$window.scrollTop should be $window.scrollTo, otherwise it says it doesn't exist. This solution works great!
@JamesGentes i needed this one too. Thanks.
R
Rizwan Jamal

This code worked great for me .. I hope it will also work great for you .. All you have to do is just inject $anchorScroll to your run block and apply listener function to the rootScope like I have done in the below example ..

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

Here's the calling order of Angularjs Module:

app.config() app.run() directive's compile functions (if they are found in the dom) app.controller() directive's link functions (again, if found)

RUN BLOCK get executed after the injector is created and are used to kickstart the application.. it means when you redirected to the new route view ,the listener in the run block calls the

$anchorScroll()

and you can now see the scroll starts to the top with the new routed view :)


Finally, a solution that is working with sticky-headers! Thank you!
w
wkonkel

After an hour or two of trying every combination of ui-view autoscroll=true, $stateChangeStart, $locationChangeStart, $uiViewScrollProvider.useAnchorScroll(), $provide('$uiViewScroll', ...), and many others, I couldn't get scroll-to-top-on-new-page to work as expected.

This was ultimately what worked for me. It captures pushState and replaceState and only updates scroll position when new pages are navigated to (back/forward button retain their scroll positions):

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

This works great for my use case. I'm using Angular UI-Router with nested views (several levels deep). Thanks!
FYI - you need to be in HTML5 mode in angular to use pushState: $locationProvider.html5Mode(true); @wkronkel is there a way to accomplish this when not using HTML5 mode in angular? Cause page reload are throwing 404 errors due to html 5 mode being active
@britztopher You can hook into the built-in events and maintain your own history, right?
where do you append this .run to? the angular your app object?
@Philll_t: Yes, the run function is a method available on every angular module object.
J
James

Here is my (seemingly) robust, complete and (fairly) concise solution. It uses the minification compatible style (and the angular.module(NAME) access to your module).

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

PS I found that the autoscroll thing had no effect whether set to true or false.


Hmmm.. this scrolls the view to the top first and then the view changes on the screen for me.
Clean solution I was looking for. If you click "back" it takes you to where you left off - this might not be desirable for some, but I like it in my case.
L
Léon Pelletier

Using angularjs UI Router, what I'm doing is this:

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

With:

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

It never fails on any page, and it is not global.


Great idea, you can make it shorter using: onEnter: scrollContent
What do you put where you wrote // Your favorite scroll method here ?
Good timing: I was two lines above this comment in the original code, trying ui-router-title. I use $('html, body').animate({ scrollTop: -10000 }, 100);
Haha, great! Strange tho, it does not work for me all the time. I have some views that are so short they don't have a scroll, and two views that have a scroll, when I place onEnter: scrollContent on each view it only works on the first view/ route, not the 2nd one. Strange.
I'm not sure, no, It just doesn't scroll to top when it should. When I click between the 'routes' some of them are still scrolling to where the ng-view is, rather than the absolute top of the enclosing page. Does that make sense?
C
Community

FYI for for anyone coming across the problem described in the title (as I did) who is also using the AngularUI Router plugin...

As asked and answered in this SO question, the angular-ui router jumps to the bottom of the page when you change routes.
Can't figure out why page loads at bottom? Angular UI-Router autoscroll Issue

However, as the answer states, you can turn off this behavior by saying autoscroll="false" on your ui-view.

For example:

<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


Mine doesn't jump to the bottom, it just retains the scroll position instead of going to the top.
This doesn't answer the question. I've got the same problem - when the route changes the scroll level just stays in the same place instead of scrolling to the top. I can get it to scroll to the top, but only by changing the hash, which I don't want to do for obvious reasons.
C
Colzak

If you use ui-router you can use (on run)

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

I know this should work, I am extensively using "$stateChangeSuccess", but for some reason $window.scrollTo(0,0) isn't working with that.
C
CodeIsLife

you can use this javascript

$anchorScroll()

Well, it's not that simple in angularjs. Your solution is only valid in jquery. What I'm looking for is an elegant way to do this in angularjs
Have you tried using $anchorScroll(), it's documented docs.angularjs.org/api/ng.%24anchorScroll.
FYI If you specify a location with $location.hash('top') before $anchorScroll() the default forward/back browser buttons no longer work; they keep redirecting you to the hash in the URL
V
Vince Verhoeven

I found this solution. If you go to a new view the function gets executed.

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

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

a
asp_net

Setting autoScroll to true did not the trick for me, so I did choose another solution. I built a service that hooks in every time the route changes and that uses the built-in $anchorScroll service to scroll to top. Works for me :-).

Service:

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

Registration:

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

B
BuffMcBigHuge

A little late to the party, but I've tried every possible method, and nothing worked correctly. Here is my elegant solution:

I use a controller that governs all my pages with ui-router. It allows me to redirect users who aren't authenticated or validated to an appropriate location. Most people put their middleware in their app's config, but I required some http requests, therefore a global controller works better for me.

My index.html looks like:

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

My initCtrl.js looks like:

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

I've tried every possible option, this method works the best.


This is the only one that worked with angular-material for me, also placing the id="top-nav" on the correct element was important.
T
Tiw

Simple Solution, add scrollPositionRestoration in the main route module enabled.
Like this:

const routes: Routes = [

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

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

D
Dev93

All of the answers above break expected browser behavior. What most people want is something that will scroll to the top if it's a "new" page, but return to the previous position if you're getting there through the Back (or Forward) button.

If you assume HTML5 mode, this turns out to be easy (although I'm sure some bright folks out there can figure out how to make this more elegant!):

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

The way it works is that the router assumes it is going to scroll to the top, but delays a bit to give the browser a chance to finish up. If the browser then notifies us that the change was due to a Back/Forward navigation, it cancels the timeout, and the scroll never occurs.

I used raw document commands to scroll because I want to move to the entire top of the window. If you just want your ui-view to scroll, then set autoscroll="my_var" where you control my_var using the techniques above. But I think most people will want to scroll the entire page if you are going to the page as "new".

The above uses ui-router, though you could use ng-route instead by swapping $routeChangeSuccess for$stateChangeSuccess.


How do you implement this? Where would you put it in regular angular-ui routes code such as the following? 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' }) });
hmmm... didn't work :-( It just kills the app. .state('web', { url: '/webdev', templateUrl: 'partial-web.html', controller: function($scope) { $scope.clients = ['client1', 'client2', 'client3']; // I put it here } }) I'm just learning this stuff, maybe I'm missing something :D
@AgentZebra At a minimum, the controller is going to need to take $timeout as an input (since my code references it), but perhaps more importantly, the controller I'm mentioning needs to live at a level above an individual state (you could include the statements in your top-level controller). The initial AngularJS learning curve is indeed very difficult; good luck!
a
aBertrand

None of the answer provided solved my issue. I am using an animation between views and the scrolling would always happen after the animation. The solution I found so that the scrolling to the top happen before the animation is the following directive:

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

            })
        }
    };
}]);

I inserted it on my view as follows:

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

T
ToughPal

Try this http://ionicframework.com/docs/api/service/$ionicScrollDelegate/

It does scroll to the top of the list scrollTop()


A
Augie Gardner

I have finally gotten what I needed.

I needed to scroll to the top, but wanted some transitions not to

You can control this on a route-by-route level.
I'm combining the above solution by @wkonkel and adding a simple noScroll: true parameter to some route declarations. Then I'm catching that in the transition.

All in all: This floats to the top of the page on new transitions, it doesn't float to the top on Forward / Back transitions, and it allows you to override this behavior if necessary.

The code: (previous solution plus an extra 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');

Put that in your app.run block and inject $state... myApp.run(function($state){...})

Then, If you don't want to scroll to the top of the page, create a route like this:

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

T
Teja

This Worked for me including autoscroll

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