Search code examples
javascriptjqueryframe-rategsap

How to reduce FPS drops in javascript scroll / CSS transitions


I've gotten myself down the rabbit hole of performance analysis and optimization and I am unable to reduce the drop in frame rate on what should be a fairly lightweight transform, although I suspect the fact it is on the scroll, albeit throttled is possibly to blame?

It is this simple slow/smooth scroll effect I want to achieve, shown here: JSFiddle

The un-throttled jQuery version..

JS

w = $(window);
b = $('body');
c = $('#container');

$(document).ready(function() {

    w.scroll(function() {
        c.css('transform','translateY(' + -w.scrollTop() + 'px)');
    });

});

CSS

html, body { margin: 0; }

body {
    height: 500vh;
}

#container {
    display: block;
    position: fixed;
    height: 500vh;
    width: 100vw;
    background: #814e4e;
    padding: 20%;

    font-size: 30px;
    line-height: 45px;
    color: #fff;
    text-align: center;

    transform: translate3d(0,0,0);
    will-change: transform;
    transition: transform 1.5s ease-out;
}

HTML

<body>
  <div id="container">
      <p>Lorem ipsum...</p>
  </div>
</body>

I have snipped out the throttle function from underscore.js, as well as re-writing the entire thing in GSAP, and in all cases, to varying degrees, there's an average FPS drop of 10-20. Surprisingly the throttled jquery version was not massively improved on the normal jquery on the scroll function, and the GSAP version, using their new scroll trigger plugin, was a lot less smooth than either of the above. Any advice on this or other recommended approaches I can try would be appreciated!


Solution

  • .scrollTop causes reflow and is not performant. That's your main issue.

    Secondly, you're using the .scroll function which creates a new animation affecting the scroll position every tick so that's not performant either.

    Thirdly, you're applying a CSS transition which tries to animate the property every tick so that's not performant either.

    If you fix all of the above, you should still throttle the scroll event.

    Much more minorly, jQuery is slower than vanilla JS.


    Since you tagged this with GSAP and made an attempt with ScrollTrigger, here's how you can do it with ScrollTrigger. It's a demo I made previously but the effect is the same.

    Some keys about why it is more performant:

    • ScrollTrigger calculates when the animation should start and end and only animates during that part of the page.
    • Scroll events are debounced. Updates are synchronized with screen refreshes.
    • All scroll calculations are done in the same update method.
    • The page's natural scroll position is not affected.
    • GSAP automatically uses 3D transforms to make use of rending on the GPU.
    • Any previous animations that are created are killed off by future animations.
    • There's no conflict between the positioning set by JS and CSS trying to animate things at the same time.