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

26 Feb · by Tim Kamanin · 3 min read

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 ( - header_height < $(scrollTarget).scrollTop()) {
  $(scrollTarget).animate({scrollTop: ( - header_height)}, 500);

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 file add scripts[] = js/custom_views_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?


Required for comment verification


I hacked the ajax_view.js module to accomplish this. I'm glad you showed me a better way! Thank you.

Reply · 2 years, 8 months ago
Dieter Blomme

Different filename in step 2 and 3? :)

Reply · 2 years, 9 months ago

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
 * - Adding setPageURL is our custom solution and depends on our Views bugfix
 *   from
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.

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 Until this is solved there are two workarounds:

  1. Fix Views according to or
  2. Set the Views pager ID to a non-zero value.
Reply · 3 years, 9 months ago

Thank you! Thank you! Thank you!

Reply · 3 years, 9 months ago

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

Reply · 3 years, 9 months ago

I've made this little contrib module. It can be an alternative:

Reply · 3 years, 9 months ago