How to persist scrolls with turbolinks?

I noticed that sometimes scroll is going up and then only down. This version prevents such behaviour:

const persistScrollDataAttribute = 'turbolinks-persist-scroll';
let scrollPosition = null;

const turbolinksPersistScroll = () => {
  if (scrollPosition) {
    window.scrollTo(0, scrollPosition);
    scrollPosition = null;
  }


  const elements = document.querySelectorAll(`[data-${persistScrollDataAttribute}="true"]`)
  for (let i = 0; i < elements.length; i++) {
    elements[i].addEventListener('click', () => {
      document.addEventListener("turbolinks:before-render", () => {
        scrollPosition = window.scrollY;
      }, {once: true})
    })
  }
}

document.addEventListener('turbolinks:load', turbolinksPersistScroll);
document.addEventListener('turbolinks:render', turbolinksPersistScroll);

My solution in ES6:

const turbolinksPersistScroll = () => {
  const persistScrollDataAttribute = 'turbolinks-persist-scroll'
  let scrollPosition = null
  let enabled = false

  document.addEventListener('turbolinks:before-visit', (event) => {
    if (enabled)
      scrollPosition = window.scrollY
    else
      scrollPosition = null
    enabled = false
  })

  document.addEventListener('turbolinks:load', (event) => {
    const elements = document.querySelectorAll(`[data-${persistScrollDataAttribute}="true"]`)
    for (let i = 0; i < elements.length; i++) {
      elements[i].addEventListener('click', () => {
        enabled = true
      })
    }

    if (scrollPosition)
      window.scrollTo(0, scrollPosition)
  })
}

turbolinksPersistScroll()

And add on your links data-turbolinks-persist-scroll=true on links you want persist scrollbar position.

<a href="..." data-turbolinks-persist-scroll=true>Link</a>

This works for me, also with link_to remote: true.


It seems like there are two approaches to this problem.

  1. Preserve flagged elements (@vedant1811's answer)
  2. Preserve body scroll for flagged links

The second approach is the one that I've been looking for and couldn't find anywhere, so I'll provide my answer to that here.

The solution here is very similar to that of the first approach, but perhaps a little simpler. The idea is to grab the current scroll position of the body when an element is clicked, and then scroll to that position after the page is loaded:

Javascript

Turbolinks.scroll = {}

$(document).on('click', '[data-turbolinks-scroll=false]', function(e){
  Turbolinks.scroll['top'] = $('body').scrollTop();
})

$(document).on('page:load', function() {
  if (Turbolinks.scroll['top']) {
    $('body').scrollTop(Turbolinks.scroll['top']);
  }
  Turbolinks.scroll = {};
});

Markup

<a href='/' data-turbolinks-scroll='false'>Scroll preserving link</a>

I use a scroll attribute on the Turbolinks object to store my scroll position when a [data-turbolinks-scroll=false] link is clicked, then after I scroll the page I clear this attribute.

It is important that you clear the attribute (Turbolinks.scroll = {}) otherwise, subsequent clicks on non-flagged anchor links will continue to scroll you to the same position.

Note: depending on the specific styling of html and body you may need to use the scroll offset from both. An example of how this might be accomplished is:

Turbolinks.scroll = {};

$(document).on('click', '[data-turbolinks-scroll=false]', function (e) {
    Turbolinks.scroll['top'] = {
        html: $("html").scrollTop(),
        body: $("body").scrollTop()
    }
});

$(document).on('turbolinks:load', function() {
    if (Turbolinks.scroll['top']) {
        $('html').scrollTop(Turbolinks.scroll['top']['html']);
        $('body').scrollTop(Turbolinks.scroll['top']['body']);
    }
    Turbolinks.scroll = {};
});

Use the following javascript to persist scrolls. I have created a selector that matches all elements with class turbolinks-disable-scroll. Before loading,the script saves the scroll position and after loading it loads the saved positions.

// persist scrolls
// pirated from https://github.com/turbolinks/turbolinks-classic/issues/205
var elementsWithPersistentScrolls, persistentScrollsPositions;

elementsWithPersistentScrolls = ['.turbolinks-disable-scroll'];

persistentScrollsPositions = {};

$(document).on('turbolinks:before-visit', function() {
    var i, len, results, selector;
    persistentScrollsPositions = {};
    results = [];
    for (i = 0, len = elementsWithPersistentScrolls.length; i < len; i++) {
        selector = elementsWithPersistentScrolls[i];
        results.push(persistentScrollsPositions[selector] = $(selector).scrollTop());
    }
    return results;
});

$(document).on('turbolinks:load', function() {
    var results, scrollTop, selector;
    results = [];
    for (selector in persistentScrollsPositions) {
        scrollTop = persistentScrollsPositions[selector];
        results.push($(selector).scrollTop(scrollTop));
    }
    return results;
});

Tags:

Turbolinks