In my page I have two tables of same width, and them both do horizontal scroll. I need to set both tables to the same position when each one scrolls.
Actually, my code is:
var scrollA = $('#scrollA'),
scrollB = $('#scrollB');
scrollA.on('scroll', function() {
scrollB[0].scrollLeft = scrollA[0].scrollLeft;
});
It works. The problem is that there are situations where the loaded data is big enough to slow the browser and the scroll event.
Then, I'm trying to improve the user experience in those situations.
I did this snippet where I try to use the __defineSetter__
function of Object
:
var elementA = { scrollLeft: 30 },
elementB = { scrollLeft: 30 };
elementA.__defineSetter__('scrollLeft', val => elementB.scrollLeft = val);
elementA.scrollLeft += 5;
console.log(elementA.scrollLeft + ' == ' + elementB.scrollLeft);
But as you can see running the snippet, it sets undefined
to elementA.scrollLeft
and NaN
to elementB.scrollLeft
.
I'd like some guidance here, thanks for your time.
Scroll events can fire at a high rate, so you may get smoother scrolling by throttling the scroll event handler, as trincot said in his answer. You can throttle a function using setTimeout
. When moving things on the screen or dealing with other visible effects, though, I prefer requestAnimationFrame
.
requestAnimationFrame
runs the callback before the next repaint, so the browser can do some optimizations.
This code will get you closer to the solution, it is based on this example from MDN:
var scrollA = $('#scrollA'),
scrollB = $('#scrollB'),
running = false;
scrollA.on('scroll', function() {
// if we already requested an animation frame, do nothing
if (!running) {
window.requestAnimationFrame(function() {
// synchronize table B and table A
scrollB[0].scrollLeft = scrollA[0].scrollLeft
// we're done here and can request a new frame
running = false;
});
}
running = true;
});
You can use the timestamp passed to the callback to further debounce the synchronization, but you may end with a kinda sluggish scroll in the second table if you put too much delay.
Your call to __defineSetter__
replaces the property scrollLeft with one with the same name, which means you cannot get to the original property any more. Once you have called __defineSetter__
, any reading of the property results in undefined
:
console.log(scrollA[0].scrollLeft); // some number, like 10
scrollA[0].__defineSetter__('scrollLeft', val => scrollB[0].scrollLeft = val)
console.log(scrollA[0].scrollLeft); // undefined
Also, the browser does not change the scrollLeft value via an assignment as you would do in JavaScript, so the setter function will not be called during a scroll event. You can verify this by putting a console.log
in the setter function: nothing is displayed when you scroll:
scrollA[0].__defineSetter__('scrollLeft', val => console.log(val))
For the above two reasons there is no hope you can capture scrolling changes using __defineSetter__
. You need to listen to the scroll
event like you did first.
You might get better results if you do the synchronisation asynchronously, and skip some of the updates when you have many scroll events occurring:
var scrollA = $('#scrollA'),
scrollB = $('#scrollB'),
tmr = -1;
scrollA.on('scroll', function() {
clearTimeout(tmr);
tmr = setTimeout(_ => scrollB[0].scrollLeft = scrollA[0].scrollLeft, 0);
});
Now when the browser generates more than one scroll event instantly, the second one will pre-empty your scheduled update of scrollB[0].scrollLeft
: the clearTimeout
will prevent that update from happening, and the next one is scheduled instead.
You could play with the delay argument of setTimeout
to find the right balance:
Increasing the delay will prevent events from stacking up, and so scrollB will then synchronise in a more responsive way, but fewer updates will be made to scrollB's scrollLeft property, which can make it scroll less fluently.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With