Fixing Views' Scroll to Top When You Have a Fixed Header

Drupal Views offers us a cool feature: ajaxified pagers. When you click on a pager, it changes the page without reloading the main page itself and then scrolls to the top of the view. It works great, but sometimes you may encounter a problem: if you have a fixed header on your page (the one that stays on top when you scroll the page) it will overlap with the top of your view container thus scroll to top won't work preciselly correct and the header will cover the top part of your view.

I've just encountered that problem and making a note here for the future myself and, probably yourself, about how I solved this problem.

If you'll look into the views internal, you'll see it uses internal Drupal JS Framework command called viewsScrollTop that's responsible for scrolling to the top of the container. What we need here is to override this command to add some offset to the top of our view.

1. Overriding JS Command

Thankfully, Views is flexible enough and provides hook_views_ajax_data_alter() so we can alter js data and commands before they got sent to the browser, let's overwrite viewsScrollTop command with our own. In your custom module put something like this:

/**
 * This hook allows to alter the commands which are used on a views ajax
 * request.
 *
 * @param $commands
 *   An array of ajax commands
 * @param $view view
 *   The view which is requested.
 */
function MODULE_NAME_views_ajax_data_alter(&$commands, $view) {
  // Replace Views' method for scrolling to the top of the element with your
  // custom scrolling method.
  foreach ($commands as &$command) {
    if ($command['command'] == 'viewsScrollTop') {
      $command['command'] = 'customViewsScrollTop';
    }
  }
}

Now, everytime Views emits viewsScrollTop command, we replace it with our own custom one customViewsScrollTop.

2. Creating custom JS command

Ok, custom command is just a JS function attached to Drupal global object, let's create a js file and put it into it:

(function ($) {
  Drupal.ajax.prototype.commands.customViewsScrollTop = function (ajax, response, status) {
    // Scroll to the top of the view. This will allow users
    // to browse newly loaded content after e.g. clicking a pager
    // link.
    var offset = $(response.selector).offset();
    // We can't guarantee that the scrollable object should be
    // the body, as the view could be embedded in something
    // more complex such as a modal popup. Recurse up the DOM
    // and scroll the first element that has a non-zero top.
    var scrollTarget = response.selector;
    while ($(scrollTarget).scrollTop() == 0 && $(scrollTarget).parent()) {
      scrollTarget = $(scrollTarget).parent();
    }
    var header_height = 90;
    // Only scroll upward
    if (offset.top - header_height < $(scrollTarget).scrollTop()) {
      $(scrollTarget).animate({scrollTop: (offset.top - header_height)}, 500);
    }
  };
})(jQuery);

As you may see, I just copied the standard Drupal.ajax.prototype.commands.viewsScrollTop function and added header_height variable that equals to the offset/fixed header height. You may play with this value and set it according to your own taste.

Note the name of the function Drupal.ajax.prototype.commands.customViewsScrollTop, the last part should match your custom command name. Save the file in your custom module dir, in my case it's: custom_views_scroll.js

3. Attaching JS to the view

There are multiple ways to do it, let's go with with the simplest one, to your custom_module.info file add scripts[] = js/zukunft_scroll.js and clear caches, that'll make this file to be autoloaded on every page load.

That's all, since now, your views ajax page scrolls should be powered by your customViewsScrollTop instead of stock viewsScrollTop, see the difference?

Comments

Submitted by tarekdj on Wed, 2014-02-26 20:47

I've made this little contrib module. It can be an alternative: https://drupal.org/project/scroll_to_top

Submitted by Tim on Thu, 2014-02-27 15:21

Man, read the post closer, this is not about usual scroll to top, thanks for your contrib, but this is not the same I'm talking about.

Submitted by RdeBoer on Wed, 2014-02-26 23:22

Nice one Tim!
Didn't know about hook_views_ajax_data_alter().

Submitted by Carol on Tue, 2014-04-01 17:12

Thank you! Thank you! Thank you!

Submitted by lmeurs on Thu, 2014-05-15 16:01

Hi Tim,

Thanks for this great example! I liked your approach and might have made it more awesome :-) by also changing the URL in the browser's address bar, so:

  1. Visitors can copy the URL containing all current parameters.
  2. Visitors can navigate away and return to the view in the last state.

When we alter the Views' command, we also add a command of our own and sends the current page's URL:

/**
 * Implements hook_views_ajax_data_alter().
 *
 * This hook allows to alter Views AJAX commands.
 * - Replacing viewsScrollTop is inspired by timonweb.com/node/148.
 * - Adding setPageURL is our custom solution and depends on our Views bugfix
 *   from drupal.org/node/1786524#comment-8782777.
 */
function sitemodule_views_ajax_data_alter(&$commands, $view) {
  // Iterate through commands.
  foreach ($commands as &$command) {
    // If Views added the viewsScrollTop command.
    if ($command['command'] == 'viewsScrollTop') {
      // Change this for our custom AJAX command.
      $command['command'] = 'customViewsScrollTop';
 
      // Add command to set a new page URL.
      $commands[] = array(
        'command' => 'setPageURL',
        'url' => url($_GET['q'], array('query' => drupal_get_query_parameters())),
      );
 
      // Break loop.
      break;
    }
  }
}

The javascript looks like:

  /**
   * Custom AJAX command that changes the URL in the browser's address bar.
   */
  Drupal.ajax.prototype.commands.setPageURL = function (ajax, response, status) {
    // If browser supports history.replaceState, all modern browsers and IE10+.
    if (history && typeof(history.replaceState) == 'function') {
      // Add optional hash to URL.
      if (document.location.hash) {
        response.url += document.location.hash;
      }
 
      // Replace current URL in the browser's address bar.
      history.replaceState({}, null, response.url);
    }
  }

Unfortunately there is a small problem with Views (bug?), see https://drupal.org/node/1786524. Until this is solved there are two workarounds:

  1. Fix Views according to https://drupal.org/node/1786524#comment-8782777 or
  2. Set the Views pager ID to a non-zero value.

Add new comment

You are here