scroll-behavior icon indicating copy to clipboard operation
scroll-behavior copied to clipboard

Window scroll listener causes scroll stuttering in Chrome

Open pieh opened this issue 6 years ago • 13 comments

This is cross-posted issue from https://github.com/gatsbyjs/gatsby/issues/12520

Description

In Chrome DevTools using the FPS meter, the scroll listener that Gatsby seems to add causes poor scrolling performance. I noticed this on my website and on React's website. I don't add a scroll listener anywhere so I guess it's coming from Gatsby.

Steps to reproduce

  1. Visit atomiks.github.io/tippyjs
  2. Open FPS Meter in Chrome DevTools
  3. For me it's showing ~45 - 55 FPs with lots of frame drops and stuttering
  4. Type this into the console
getEventListeners(window).scroll.forEach(({ listener }) => {
  window.removeEventListener('scroll', listener)
})
  1. Much improved scrolling, way less frame drops once the listener has been removed.

Before: gyazo.com/57edc6fb31e1e06725f7e31c3b2a51a0 After: gyazo.com/3b4070170309a1c0037272a3cb3d3d17

I don't have any context on internals of scroll-behavior package, but maybe adding some debounce to listener function could help?

/cc @atomiks

pieh avatar Mar 12 '19 22:03 pieh

It is debounced, no? We only do work if the rAF handle isn’t set. If you have a test setup, maybe try making it passive? It shouldn’t help, but maybe it will.

taion avatar Mar 12 '19 22:03 taion

Is it debounced? Maybe dom-helpers/events/on debounce it, but I didn't find debounce in scroll-behavior code from quick scan - but as I mentioned I don't have much context, and I probably am wrong.

I just updated issue description with content of issue that was reported in Gatsby so it's all in one place - I should have done this from the start.

pieh avatar Mar 12 '19 22:03 pieh

I mean, “not doing anything if the timer is already set” is pretty well debounced, no?

taion avatar Mar 12 '19 22:03 taion

It looks like the function calls from this package are taking <100 microseconds so not sure what could cause it. Every so often there's one or two 30+ms frames

With listener (here there was a long period of dropped frames):

img

Without listener:

img


Chrome still drops frames without it but not as many it seems. It's also completely random. Maybe it's just Chrome's fault!

atomiks avatar Mar 13 '19 05:03 atomiks

Okay, thinking about this a little more – a lot of the work in the rAF handler (which is causing the stutter) on window scroll is to work around quirks in Firefox (when using hash navigation). Probably the best thing to do is to be conservative about when to save window scroll position.

Ick.

The best thing to do would be to only save window position before navigation in cases where we know that this is safe. This really depends on details of when e.g. popstate events fire, though.

taion avatar Mar 13 '19 13:03 taion

I mean, “not doing anything if the timer is already set” is pretty well debounced, no?

The scroll event is fired at about the same rate as the animation frames, so theoretically this will save the scroll position up 60 times per second (on a 60 Hz monitor) which seems a bit much, and sometimes causes constant style recalculations in scrollLeft.js. I noticed this while doing some performance critical animation work that was tied to the scroll position.

Would it be possible to have timeout based debounced scroll event handler instead? I would rather have a 300+ ms (or something) delay on scroll position saving than degraded scroll performance.

sebpettersson avatar Mar 27 '19 09:03 sebpettersson

That sounds like a good idea to me. Ideally we should avoid checking/saving stuff while the user is actively scrolling as much as possible.

taion avatar Mar 27 '19 16:03 taion

Care to take a stab at a PR?

taion avatar Mar 27 '19 16:03 taion

Sure! It might take a few days before I have time to look at it though.

sebpettersson avatar Mar 28 '19 05:03 sebpettersson

The first thing was to realize that the page was not scrolling on click. I was able to test this with window.pageYOffset in the console to get the currents scroll position. As the #acctab-description tab was being hidden on click of #acctab-additional tab. this created the illusion of the page scrolling as the documents height was being reduced. add the below JavaScript on the click event of the tabs to scroll the page to the correct position.

// offset of elements above the tabs var offset1 = jQuery("div.product-secondary-column.grid12-3").offset().top; var offset2 = jQuery("div.product-shop.grid12-5").offset().top; var offset3 = jQuery("div.product-img-box.grid12-4").offset().top; var offsetTotal = offset1 + offset2 + offset3;

jQuery(".acctab:not(.current)").click(function(){
    if(window.innerWidth < 977){
        var body = jQuery("html, body");
        body.animate({scrollTop:offsetTotal}, '10000', 'swing');
    }
});

aousaja avatar Mar 30 '19 19:03 aousaja

requestIdleCallback would be good, if there were a good polyfill.

taion avatar Apr 05 '19 22:04 taion

It probably creates a minor performance issue on firefox android https://github.com/webcompat/web-bugs/issues/47941

karlcow avatar Mar 20 '20 04:03 karlcow

@taion I'm also experiencing this. #147 adds debouncing but seems outdated and do not address elements registered with registerElement.

satazor avatar Aug 25 '20 00:08 satazor