How to develop high performance onScroll event?

Jun 19, 2015

We need to take special care of the performance while hooking up to the onscroll event as it fires more frequently with shorter interval time comparing to other events like mouse and keyboard. The FPS drops if your onscroll event contains elaborate logic or animations. In addition, user usually scrolls the mouse wheel continuously, it may aggravate the FPS drop, increase the browser CPU usage and impact the user experience. Developers sometimes don't notice such kind of issue since most development machines have better performance than normal user machines. Next, let's talk about how to prevent the performance issue caused by onscroll event.

Limit the minimum execution interval time of ONSCROLL event

The onscroll event trigger interval time is around 10~20ms when we scrolling the mouse wheel. However, in most cases we don't need the onscroll event to execute such frequently. We can use following code to set a minimum execution interval time for onscroll event.

var scroll = function () {
    // do the onscroll stuff you want here
};
var waiting = false;
$(window).scroll(function () {
    if (waiting) {
        return;
    }
    waiting = true;

    scroll();

    setTimeout(function () {
        waiting = false;
    }, 100);
});

The code above declares a variable: waiting as a flag, which limits the scroll() function to execute with a minimum interval time: 100ms.

However it has a disadvantage that the last onscroll event of a scrolling action may be in the waiting period, hence the last scroll() function would never fire. If your scroll() function has to execute to match the scrollbar position in a timely manner, you can schedule an extra execution of the scroll() function after 200ms in the onscroll event as below.

var scroll = function () {
    // do the onscroll stuff you want here
};
var waiting = false, endScrollHandle;
$(window).scroll(function () {
    if (waiting) {
        return;
    }
    waiting = true;

    // clear previous scheduled endScrollHandle
    clearTimeout(endScrollHandle);

    scroll();

    setTimeout(function () {
        waiting = false;
    }, 100);

    // schedule an extra execution of scroll() after 200ms
    // in case the scrolling stops in next 100ms
    endScrollHandle = setTimeout(function () {
        scroll();
    }, 200);
});

Use requestAnimationFrame

I would strongly recommend you to use requestAnimationFrame to trigger the scroll() function if your website only needs to run with modern browsers. The browser repaints for each frame, requestAnimationFrame allows you to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. This ensures not only the scroll function executing at a proper time point but also the smooth scrolling experience.

var scroll = function () {
    // do the onscroll stuff you want here
};
var raf = window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    window.oRequestAnimationFrame;
var $window = $(window);
var lastScrollTop = $window.scrollTop();

if (raf) {
    loop();
}

function loop() {
    var scrollTop = $window.scrollTop();
    if (lastScrollTop === scrollTop) {
        raf(loop);
        return;
    } else {
        lastScrollTop = scrollTop;

        // fire scroll function if scrolls vertically
        scroll();
        raf(loop);
    }
}

Save the calculations inner ONSCROLL event

Although using the methods above is able to structurally prevent potential onscroll event performance issue, but we still need to optimize the scroll() function as much as possible so that each scroll() function can finish quickly. Things like variable initialization, calculations which don't rely on scrollbar position should be excluded from the onscroll event.

var scroll = function () {
    if ($(window).scrollTop - $('#header').height() > 1000) {
        $('#navigator').css('position', 'fixed');
    }
};
var headerHeight = $('#header').height();
var $navigator = $('#navigator');
var $window = $(window);
var scroll = function () {
    if ($window.scrollTop() - headerHeight > 1000) {
        $navigator.css('position', 'fixed');
    }
};

The first piece of code above is a bad example, we can cache the jQuery object and the height value of a static element outside the scroll() function.