Search code examples
ember.js

Hot to scroll to window-top on route load in Emberjs 5.4


In Ember 5.4, when a new route is loaded, and the last view was scrolled, the scroll position remains. I want it to scroll to top of the window instantly if the url/route changed.

I made it work with this code in app/routes/application.js:

import Route from '@ember/routing/route';
import { action } from '@ember/object';

export default class ApplicationRoute extends Route {
    @action
    didTransition() {
        setTimeout (() => window.scrollTo ({top: 0, left: 0, behavior: 'instant'}), 1);
    }
}

But using setTimeout with 1 millisecond seems bad style and maybe error-prone to me. Nevertheless just using window.scrollTo ({top: 0, left: 0, behavior: 'instant'}) without timeout does not work, it does not scroll the window to the top.

So I think I maybe am using the wrong event(/action), but I cant find a better one in the docs (e.g. here: https://api.emberjs.com/ember/5.4/classes/Route).

This problem is already addressed in some other stackoverflow-questions (e.g. here: Emberjs scroll to top when changing view) but for older versions of ember or an other style of defining the route - where, to be honest, I'm not sure what exactly in applicable, because I am new to ember and was not able to find my way through the jungle of versions and deprecations docs and different styles in different versions to get an answer to this question.


Solution

  • The fastest (and a bit naiive) way to achieve this is to use routeDidChange from the RouterService (accessed via @service router).

    Some place early on in your app's boot, you can configure the scroll behavior:

    // in an existing file, app/router.js
    // this doesn't use the ember router, so idk if I'd recommend this
    import EmberRouter from '@ember/routing/router';
    
    class Router extends EmberRouter {
      // ...
    
      constructor() {
        super(...arguments);
        this.on('routeDidChange', () => window.scrollTo(0, 0));
      }
    }
    

    or

    # generate the normally implicit, yet still top-level route
    ember g route application
    
    import Route from '@ember/routing/route';
    import { service } '@ember/service';
    
    export default class ApplicationRoute extends Route {
      @service router;
    
      // initial app set up can go in here
      beforeModel() {
        this.router.on('routeDidChange', () => window.scrollTo(0, 0));
      }
    }
    

    something to keep in mind is that navigation and scroll is typically state-ful, so with the above code and when clicking the back button, you'll still be scrolled to the top of the page. Some folks prefer this, some don't. The browser-default is to maintain scroll position, which will require maintaining state on history about the scroll position -- which is a bit tricky because folks tend to resize their windows, and that changes the scroll coordinates, breaking any state you'd be able to keep track of.