11

Sometimes it is desirable to persist scroll positions between page visits.

Turbolinks resets scroll position after loading the data.

How can I disable it for particular elements?

Vedant Agarwala
  • 18,146
  • 4
  • 66
  • 89

4 Answers4

8

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.

Pioz
  • 6,051
  • 4
  • 48
  • 67
2

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;
});
Vedant Agarwala
  • 18,146
  • 4
  • 66
  • 89
  • For anyone using Turbolinks 5, replace the event 'page:load' with 'turbolinks:load'. Works like a charm, thank you :) – Paul Leader Feb 21 '17 at 23:04
  • 1
    This works as expected, however the only issue I have is that it sometimes "jumps" the disabled element when loading a new page, where it starts at the top and then half a second later jumps down to the previous scroll position. – Dan L Oct 26 '17 at 12:51
1

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 = {};
});
Greg Dean
  • 29,221
  • 14
  • 67
  • 78
gabeodess
  • 2,006
  • 21
  • 13
1

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);
quolpr
  • 143
  • 1
  • 8