scroll position in meteor
Meteor - maintain scroll position on navigation

May 2016

Single page web apps are great but one thing we loose is the native browser behaviour that returns you to your previous position, when navigating through the history.


After building a small database of gluten free restaurants/bars on www.glulessapp.com it became really frustrating when I navigated back to the list of local establishments and was always returned to the top of the page. In this post I will outline the how to maintain scroll position on page navigation, in meteor.js.

Flow-Router

The requirement to implement this scroll memory was the motivation I needed to move my app from Iron-Router to Flow-Router, and I'm really pleased that I did. With Flow-Router I've got way more control over subscriptions and it's moved me towards the pattern of incrementally loading sections of my pages, as the data comes in, rather than all the subscriptions sitting in the waitOn method, and blocking the page.

$ meteor add kadira:flow-router

The transition from Iron-Router to Flow-Router is pretty painless, so I won't go into detail here. But you can check out the docs on how to use flow here.

Make sure you also use the subs manager, so that subscriptions are cached. This will enable this scroll history code to work and will also result in a nice performance boost to your app. The Kadira blog guides through you how to use it here.

$ meteor add meteorhacks:subs-manager

So we're now using Flow-Router, and we're also using Subs-Manager. Pop the following code into a client-side file and you're rocking scroll history.

previousPathsObj = {};
exemptPaths = ['/place/']; // these are the paths that we don't want to remember the scroll position for.
function thisIsAnExemptPath(path) {
     var exemptPath = false;
     _.forEach(exemptPaths, function (d) {
         if (path.indexOf(d) >= 0) {
             exemptPath = true;
             return exemptPath;
      }
   });
  return exemptPath;
}
function saveScrollPosition(context) {
   var exemptPath = thisIsAnExemptPath(context.path);
   if (!exemptPath) {
       // add / update path
       previousPathsObj[context.path] = $('body').scrollTop();
   }
}
function jumpToPrevScrollPosition(context) {
   var path = context.path;
   var scrollPosition = 0;
   if (!_.isUndefined(previousPathsObj[context.path])) {
       scrollPosition = previousPathsObj[context.path];
   }
   if (scrollPosition === 0) {
       // we can scroll right away since we don't need to wait for rendering
       $('body').animate({scrollTop: scrollPosition}, 0);
   } else {
   // Now we need to wait a bit for blaze/react does rendering.
   // We assume, there's subs-manager and we've previous page's data.
   // Here 10 millis delay is a arbitrary value with some testing.
   setTimeout(function () {
      $('body').animate({scrollTop: scrollPosition}, 0);
      }, 10);
   }
}
FlowRouter.triggers.exit([saveScrollPosition]);
FlowRouter.triggers.enter([jumpToPrevScrollPosition]);

Full credit to kadira.io for this logic. I merely stole their idea and tweaked it to save the scroll history for multiple items.

Please enable JavaScript to view the comments powered by Disqus.