DataTables 3: scrollX header→body scroll sync cancels trackpad momentum / smooth scrolling
DataTables 3: scrollX header→body scroll sync cancels trackpad momentum / smooth scrolling
Version: DataTables 3.1.2 (also seen with FixedColumns 6.1.1 / FixedHeader 5.1.1, but they are not the cause). Chrome and Safari on macOS.
Description:
With scrollX: true, a trackpad fling (momentum scroll) on the scroll body no longer glides to the end of the table. It stops dead at a random point, and the user has to scroll again to reach the edge. The same table glides correctly on DataTables 2.3.x.
Cause:
In 2.x, _fnScrollDraw only synced the body from the header when a header cell received focus:
$('th, td', scrollHead).on('focus', function () {
scrollBody.scrollLeft = scrollHead.scrollLeft;
});
In 3.x the header (and footer) have their own scroll.DT listeners that write back to the body:
scrollHead.on('scroll.DT', () => {
let scrollLeft = scrollHead.scrollLeft();
scrollBody.scrollLeft(scrollLeft);
scrollFoot.scrollLeft(scrollLeft);
});
Every body scroll sets the header's scrollLeft, the header's scroll event fires on the next frame, and the body's scrollLeft is assigned again. Any programmatic assignment to scrollLeft, even with an unchanged value, cancels the browser's momentum or smooth scroll animation. Where the fling dies depends on frame timing, so the stop position looks random. Guarding the write with if (body.scrollLeft !== head.scrollLeft) does not help, because the header value is already a frame stale by the time its event fires.
Reproduction (no trackpad needed):
On any scrollX table, in the console:
var body = document.querySelector('div.dt-scroll-body');
body.scrollLeft = body.scrollWidth;
setTimeout(function () {
body.scrollTo({ left: 0, behavior: 'smooth' });
setTimeout(function () { console.log('ended at', body.scrollLeft); }, 2500);
}, 400);
On 3.1.2 this logs the starting position (the smooth scroll is cancelled on its first frame). After $(document.querySelector('div.dt-scroll-head')).off('scroll.DT') it logs 0.
Suggested fix:
Restore the 2.x behaviour: don't sync the body from the header's scroll event, only on header cell focus (the header is overflow: hidden, so focus is the only way a user can scroll it).
Workaround we're using:
$(document).on('init.dt', function (e, settings) {
var head = settings.scrollHead, body = settings.scrollBody, foot = settings.scrollFoot;
if (!head || !head.count()) return;
var headEl = head.get(0), bodyEl = body.get(0);
$(headEl).off('scroll.DT');
if (foot && foot.count()) $(foot.get(0)).off('scroll.DT');
$(headEl).on('focusin', 'th, td', function () { bodyEl.scrollLeft = headEl.scrollLeft; });
});
Replies
Hi,
Thanks for posting this. I remember at the time when putting the changes in for this that it is as straight forward as it appeared to be. Turns out it wasn't! I experimented extensively during the v3 development with dropping the split of the table into three parts, and just putting it into a scrolling container. This largely worked for
scrollX, but there were a whole lot of other implications that meant it just wasn't practical at the moment.I've moved back to the former approach and that will be in the next patch, which I don't expect will be far away.
Many thanks,
Allan