EdenMachine EdenMachine - 4 months ago 20
jQuery Question

More efficient way to handle $(window).scroll functions in jquery?

In the code below, I'm checking to see if the window is being scrolled past a certain point and if it is, change an element to use fixed position so that it doesn't scroll off the top of the page. The only problem is that is seems to be HIGHLY client-side-memory intensive (and really bogs down the scrolling speed) because at every single scroll pixel I am updating the style attributes over and over on the element.

Would checking if the attr is already there before attempting to update it make a significant difference? Is there a completely different and more efficient practice to get the same result?

$(window).scroll(function () {
var headerBottom = 165;
var fcHeight = $("#pnlMainNavContainer").height();

var ScrollTop = $(window).scrollTop();
if (ScrollTop > headerBottom) {
$("#HeaderContentBuffer").attr("style", "margin-top:" + (fcHeight) + "px;");
$("#AddFieldsContainer").attr("style", "position:fixed;width:320px;top:70px;left:41px;");
} else {
$("#HeaderContentBuffer").attr("style", "margin-top: 0px;");
$("#AddFieldsContainer").removeAttr("style");
}
});


As I'm typing this, I notice that StackOverflow.com using the same type of functionality with their yellow "Similar Questions" and "Help" menus on the right hand side of this page. I wonder how they do it.

Answer

One technique you can use is to set a timer on the scroll event and only do the main work when the scroll position hasn't changed for a short period of time. I use that technique on resize events which have the same issue. You can experiment with what timeout value seems to work right. A shorter time updates with shorter pauses in scrolling and thus may run more often during the scroll, a longer time requires the user to actually pause all motion for a meaningful time. You will have to experiment with what timeout value works best for your purposes and it would be best to test on a relatively slow computer since that's where the issue of scroll lag would be most pronounced.

Here's the general idea how this could be implemented:

var scrollTimer = null;
$(window).scroll(function () {
    if (scrollTimer) {
        clearTimeout(scrollTimer);   // clear any previous pending timer
    }
    scrollTimer = setTimeout(handleScroll, 500);   // set new timer
});

function handleScroll() {
    scrollTimer = null;
    var headerBottom = 165;
    var fcHeight = $("#pnlMainNavContainer").height();

    var ScrollTop = $(window).scrollTop();
    if (ScrollTop > headerBottom) {
        $("#HeaderContentBuffer").attr("style", "margin-top:" + (fcHeight) + "px;");
        $("#AddFieldsContainer").attr("style", "position:fixed;width:320px;top:70px;left:41px;");
    } else {
        $("#HeaderContentBuffer").attr("style", "margin-top: 0px;");
        $("#AddFieldsContainer").removeAttr("style");
    }
}

You may also be able to speed up your scroll function by caching some of the selectors when the scrolling first starts so they don't have to be recalculated each time. This is one place where the extra overhead of creating a jQuery object each time might not be helping you.


Here's a jQuery add-on method that handles the scrolling timer for you:

(function($) {
    var uniqueCntr = 0;
    $.fn.scrolled = function (waitTime, fn) {
        if (typeof waitTime === "function") {
            fn = waitTime;
            waitTime = 500;
        }
        var tag = "scrollTimer" + uniqueCntr++;
        this.scroll(function () {
            var self = $(this);
            var timer = self.data(tag);
            if (timer) {
                clearTimeout(timer);
            }
            timer = setTimeout(function () {
                self.removeData(tag);
                fn.call(self[0]);
            }, waitTime);
            self.data(tag, timer);
        });
    }
})(jQuery);

Working demo: http://jsfiddle.net/jfriend00/KHeZY/

Your code would then be implemented like this:

$(window).scrolled(function() {
    var headerBottom = 165;
    var fcHeight = $("#pnlMainNavContainer").height();

    var ScrollTop = $(window).scrollTop();
    if (ScrollTop > headerBottom) {
        $("#HeaderContentBuffer").attr("style", "margin-top:" + (fcHeight) + "px;");
        $("#AddFieldsContainer").attr("style", "position:fixed;width:320px;top:70px;left:41px;");
    } else {
        $("#HeaderContentBuffer").attr("style", "margin-top: 0px;");
        $("#AddFieldsContainer").removeAttr("style");
    }
});