2
\$\begingroup\$

I've been playing around synchronizing two scrollbars on a web application using JavaScript and jQuery (1.11.3). So far what I've been doing is any time a scroll event happens on one div, I use scrollTop to adjust the position on the other div:

$left = $("#left")
$right = $("#right")

$left.scroll(function () {
    $right.scrollTop($left.scrollTop());
});

$right.scroll(function () {
    $left.scrollTop($right.scrollTop());
});

(jsFiddle)

The problem comes from scrollTop triggering a scroll event on the element it's called from, creating a loop and making it misbehave on some browsers.

JavaScript events are queued, so toggling a flag before and after a call to scrollTop is not working (and could possibly backfire). So I came up with this solution:

$left = $( "#left" )
scrollLeft = []

$right = $( "#right" )
scrollRight = []

$left.scroll( function () {
    if ( !scrollRight.pop() ) {
        scrollLeft.push( true );
        $right.scrollTop( $left.scrollTop() );
    }
} );

$right.scroll( function () {
    if ( !scrollLeft.pop() ) {
        scrollRight.push( true );
        $left.scrollTop( $right.scrollTop() );
    }
} );

(jsFiddle)

This actually works, even though the scroll event is triggered on the other div the code is not executed and the loop is broken. I'm using a queue with push and pop in case some browser calls event handlers in some weird way; possibly using a single boolean variable could suffice to guard scrollTop.

My question is, is there a better way of doing this?

\$\endgroup\$
  • 1
    \$\begingroup\$ This should not have been moved to code-review. It is a definite code bug with FireFox. \$\endgroup\$ – Gone Coding Aug 28 '15 at 10:36
  • \$\begingroup\$ I'm voting to close this question as off-topic because it appears to be about a bug in the test environment. Code Review doesn't do bugs. \$\endgroup\$ – Mast Aug 29 '15 at 0:20
  • \$\begingroup\$ It's definitely not a bug, the only browser that works properly is Chrome. IE, Edge and Firefox have glitches due to how they handle the scroll event. It happens on Windows, Mac and Linux, on different computers. \$\endgroup\$ – JayC Aug 29 '15 at 0:26
2
\$\begingroup\$

As you never want to handle a triggered event, I can suggest making it simpler using a single sentinel flag:

$left = $("#left")
$right = $("#right")

// Use a simple sentinel to say we are busy handling an event
var scrolling = false;
var timer;
$left.scroll(function () {
    //console.log("left "  + $left.scrollTop() + " : " + $.now());
    if (!scrolling) {
        //console.log("SCROLLING LEFT");
        clearTimeout(timer);
        timer = setTimeout(function () {
            scrolling = true;
            $right.scrollTop($left.scrollTop());
            scrolling = false;
        }, 10);
    }
});

$right.scroll(function () {
    //console.log("Right " + $right.scrollTop() + " : " + $.now());
    if (!scrolling) {
        clearTimeout(timer);
        timer = setTimeout(function () {
            //console.log("SCROLLING RIGHT");
            scrolling = true;
            $left.scrollTop($right.scrollTop());
            scrolling = false;
        }, 10);
    }
});

Update:

With FireFox, it triggers multiple scroll events, in sequence, for a single mouse wheel move. The trick is to wait until it settles using a timer. I found 10ms seems to work fine.

JSFiddle: http://jsfiddle.net/8xaw2896/8/

\$\endgroup\$
  • \$\begingroup\$ This was my first attempt but it doesn't work. Try your fiddle on Firefox, for example, and the bars go very slow because of the event loop. The scroll event on $right is called after the handler on $left is finished, so setting scrolling to false within the handler makes it false when the scroll event is triggered on $right. Basically it's like not having a flag at all. \$\endgroup\$ – JayC Aug 28 '15 at 9:37
  • \$\begingroup\$ @JayC: Just tried on Firefox and I see what you mean. When you use the mouse wheel it does really small increments. That is odd. Will investigate on FireFox \$\endgroup\$ – Gone Coding Aug 28 '15 at 10:00
  • \$\begingroup\$ Sorted for FireFox by adding a delay (only the last position is passed on) to the other scrollbar. \$\endgroup\$ – Gone Coding Aug 28 '15 at 10:37
  • \$\begingroup\$ The second code I posted in my question actually works on all browsers. It's similar to your first idea of using flags, but the flag is unset by the other handler and there are two sets of flags scrollLeft and scrollRight. I use a stack in case the scroll is repeated and triggers more than one event. \$\endgroup\$ – JayC Aug 28 '15 at 10:41
  • \$\begingroup\$ My version avoids excessive event sending, but it's your call whether you feel it improves the situation. My work here is done :) \$\endgroup\$ – Gone Coding Aug 28 '15 at 10:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.