Synchronized scrolling using jQuery?

I am trying to implement synchronized scrolling for two DIV with the following code.

DEMO

$(document).ready(function() {
    $("#div1").scroll(function () { 
        $("#div2").scrollTop($("#div1").scrollTop());
    });
    $("#div2").scroll(function () { 
        $("#div1").scrollTop($("#div2").scrollTop());
    });
});

#div1 and #div2 is having the very same content but different sizes, say

#div1 {
 height : 800px;
 width: 600px;
}
#div1 {
 height : 400px;
 width: 200px;
}

With this code, I am facing two issues.

1) Scrolling is not well synchronized, since the divs are of different sizes. I know, this is because, I am directly setting the scrollTop value. I need to find the percentage of scrolled content and calculate corresponding scrollTop value for the other div. I am not sure, how to find the actual height and current scroll position.

2) This issue is only found in firefox. In firefox, scrolling is not smooth as in other browsers. I think this because the above code is creating a infinite loop of scroll events. I am not sure, why this is only happening with firefox. Is there any way to find the source of scroll event, so that I can resolve this issue.

Any help would be greatly appreciated.

Answers:

Answer

You can use element.scrollTop / (element.scrollHeight - element.offsetHeight) to get the percentage (it'll be a value between 0 and 1). So you can multiply the other element's (.scrollHeight - .offsetHeight) by this value for proportional scrolling.

To avoid triggering the listeners in a loop you could temporarily unbind the listener, set the scrollTop and rebind again.

var $divs = $('#div1, #div2');
var sync = function(e){
    var $other = $divs.not(this).off('scroll'), other = $other.get(0);
    var percentage = this.scrollTop / (this.scrollHeight - this.offsetHeight);
    other.scrollTop = percentage * (other.scrollHeight - other.offsetHeight);
    // Firefox workaround. Rebinding without delay isn't enough.
    setTimeout( function(){ $other.on('scroll', sync ); },10);
}
$divs.on( 'scroll', sync);

http://jsfiddle.net/b75KZ/5/

Answer

This is what I'm using. Just call the syncScroll(...) function with the two elements you want to synchronize. I found pawel's solution had issues with continuing to slowly scroll after the mouse or trackpad was actually done with the operation.

See working example here.

// Sync up our elements.
syncScroll($('.scroll-elem-1'), $('.scroll-elem-2'));


/***
*   Synchronize Scroll
*   Synchronizes the vertical scrolling of two elements.
*   The elements can have different content heights.
*
*   @param $el1 {Object}
*       Native DOM element or jQuery selector.
*       First element to sync.
*   @param $el2 {Object}
*       Native DOM element or jQuery selector.
*       Second element to sync.
*/
function syncScroll(el1, el2) {
  var $el1 = $(el1);
  var $el2 = $(el2);

  // Lets us know when a scroll is organic
  // or forced from the synced element.
  var forcedScroll = false;

  // Catch our elements' scroll events and
  // syncronize the related element.
  $el1.scroll(function() { performScroll($el1, $el2); });
  $el2.scroll(function() { performScroll($el2, $el1); });

  // Perform the scroll of the synced element
  // based on the scrolled element.
  function performScroll($scrolled, $toScroll) {
    if (forcedScroll) return (forcedScroll = false);
    var percent = ($scrolled.scrollTop() / 
      ($scrolled[0].scrollHeight - $scrolled.outerHeight())) * 100;
    setScrollTopFromPercent($toScroll, percent);
  }

  // Scroll to a position in the given
  // element based on a percent.
  function setScrollTopFromPercent($el, percent) {
    var scrollTopPos = (percent / 100) *
      ($el[0].scrollHeight - $el.outerHeight());
    forcedScroll = true;
    $el.scrollTop(scrollTopPos);
  }
}
Answer

Runs like clockwork (see DEMO)

$(document).ready(function(){

  var master = "div1"; // this is id div
  var slave = "div2"; // this is other id div
  var master_tmp;
  var slave_tmp;
  var timer;

  var sync = function ()
  {
    if($(this).attr('id') == slave)
    {
      master_tmp = master;
      slave_tmp = slave;
      master = slave;
      slave = master_tmp;
    }

    $("#" + slave).unbind("scroll");

    var percentage = this.scrollTop / (this.scrollHeight - this.offsetHeight);

    var x = percentage * ($("#" + slave).get(0).scrollHeight - $("#" + slave).get(0).offsetHeight);

    $("#" + slave).scrollTop(x);

    if(typeof(timer) !== 'undefind')
      clearTimeout(timer);

    timer = setTimeout(function(){ $("#" + slave).scroll(sync) }, 200)
  }

  $('#' + master + ', #' + slave).scroll(sync);

});
Answer

If the divs are of equal sizes then this code below is a simple way to scroll them synchronously:

scroll_all_blocks: function(e) {
        var scrollLeft = $(e.target)[0].scrollLeft;

        var len = $('.scroll_class').length;
        for (var i = 0; i < len; i++)
        {
            $('.scroll_class')[i].scrollLeft = scrollLeft;
        }

    }

Here im using horizontal scroll, but you can use scrollTop here instead. This function is call on scroll event on the div, so the e will have access to the event object. Secondly, you can simply have the ratio of corresponding sizes of the divs calculated to apply in this line $('.scroll_class')[i].scrollLeft = scrollLeft;

Answer

I like pawel's clean solution but it lacks something I need and has a strange scrolling bug where it continues to scroll and my plugin will work on multiple containers not just two.

http://www.xtf.dk/2015/12/jquery-plugin-synchronize-scroll.html

Example & demo: http://trunk.xtf.dk/Project/ScrollSync/

Plugin: http://trunk.xtf.dk/Project/ScrollSync/jquery.scrollSync.js

$('.scrollable').scrollSync();

Answer

I solved the sync scrolling loop problem by setting the scroll percentage to fixed-point notation: percent.toFixed(0), with 0 as the parameter. This prevents mismatched fractional scrolling heights between the two synced elements, which are constantly trying to "catch up" with each other. This code will let them catch up after at most a single extra step (i.e., the second element may continue to scroll an extra pixel after the user stops scrolling). Not a perfect solution or the most sophisticated, but certainly the simplest I could find.

var left = document.getElementById('left');
var right = document.getElementById('right');
var el2;
var percentage = function(el) { return (el.scrollTop / (el.scrollHeight - el.offsetHeight)) };

function syncScroll(el1) {
  el1.getAttribute('id') === 'left' ? el2 = right : el2 = left;
  el2.scrollTo( 0, (percentage(el1) * (el2.scrollHeight - el2.offsetHeight)).toFixed(0) ); // toFixed(0) prevents scrolling feedback loop
}

document.getElementById('left').addEventListener('scroll',function() {
  syncScroll(this);
});
document.getElementById('right').addEventListener('scroll',function() {
  syncScroll(this);
});

Answer

From the pawel solution (first answer).

For the horizzontal synchronized scrolling using jQuery this is the solution:

var $divs = $('#div1, #div2'); //only 2 divs
var sync = function(e){
    var $other = $divs.not(this).off('scroll');
    var other = $other.get(0);
    var percentage = this.scrollLeft / (this.scrollWidth - this.offsetWidth);
    other.scrollLeft = percentage * (other.scrollWidth - other.offsetWidth);
    setTimeout( function(){ $other.on('scroll', sync ); },10);
}

$divs.on('scroll', sync);

JSFiddle

An other solution for multiple horizontally synchronized divs is this, but it works for divs with same width.

var $divs = $('#div1, #div2, #div3'); //multiple divs
var sync = function (e) {
    var me = $(this);
    var $other = $divs.not(me).off('scroll');
    $divs.not(me).each(function (index) {
        $(this).scrollLeft(me.scrollLeft());
    });
    setTimeout(function () {
        $other.on('scroll', sync);
    }, 10);
}
$divs.on('scroll', sync);

NB: Only for divs with same width

JSFiddle

Answer

If you don't want proportional scrolling, but rather to scroll an equal amount of pixels on each field, you could add the value of change to the current value of the field you're binding the scroll-event to.

Let's say that #left is the small field, and #right is the bigger field.

var oldRst = 0;

$('#right').on('scroll', function () {
    l = $('#left');
    var lst = l.scrollTop();
    var rst = $(this).scrollTop();

    l.scrollTop(lst+(rst-oldRst)); // <-- like this

    oldRst = rst;
});

https://jsfiddle.net/vuvgc0a8/1/

By adding the value of change, and not just setting it equal to #right's scrollTop(), you can scroll up or down in the small field, regardless of its scrollTop() being less than the bigger field. An example of this is a user page on Facebook.

This is what I needed when I came here, so I thought I'd share.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.