Question

I'm attempting to create a navigation menu that uses some jQuery. I wanted keyboard users to be able to have the same experience as mouse users, so I'm duplicating the functionality found in my hover() event handler in my focus() and blur() event handlers. For some reason, this is causing noticeable lag in Firefox and IE when the user clicks on a link, which does not occur when the focus() and blur() code is taken out. How can I go about speeding this up? I've done as much optimizing as my limited javascript knowledge will permit, but I haven't seen any "speedup", so I'm thinking it's might be related to how these browsers handle the events.

Is there anything major I'm overlooking? Or are there any alternative ways to retain accessibility for keyboard users while not using these events?

        var statePad=0;

            function stateChanger(ourStatePad) {
                //the purpose of this function is to translate the current state of the menu into a different graphical representation of the menu state.
                var tempVar=ouStatePad;
                var tempArray = new Array;
                tempArray[5]=0;
                for (var x=0;x < 5;x++) {
                    tempArray[x]=tempVar % 10;
                    tempVar=(tempVar-tempArray[x])/10;
                }
                for (var arrayIndex=4;arrayIndex>=0;arrayIndex--) {
                   //Calculate the proper position in our CSS sprite, based on the each link's state, as well as it's neighbors'.
                    $(".block").eq(4 - arrayIndex)
                    .css(
                        "background-position",
                        //x-position
                        ((4 - arrayIndex) * -100) + "px " + 
                        //y-position
                        (tempArray[arrayIndex] + ((3 * tempArray[(arrayIndex) + 1]) * -30))) + "px";
                }
            }


        function hoverState(index,sign) {
            var placeholder=Math.pow(10,4-index);

            if (statePad != placeholder*2)
                statePad += (placeholder * sign);
            stateChanger(statePad);
}

        .click(function() {
            var index=$("#navbar a").index(this);
            statePad=Math.pow(10,(4-index))*2;
            stateChanger(statePad);
            $(".active").removeClass("active");
            $(this).addClass("active");
        })


        .hover(
            function () {
                hoverState($("#navbar a").index(this),1);
            },
            function () {
                hoverState($("#navbar a").index(this),-1);
            });

        $("#navbar a").focus( 
            function() {
                hoverState($("#navbar a").index(this),1);
            }
        );

        $("#navbar a").blur( 
            function() {
                hoverState($("#navbar a").index(this),-1);
            }
        );  
    });

You can check it out here

Was it helpful?

Solution 2

I solved this by complete accident, sort of. I realized that my problem wasn't really the lag, that was just a symptom of a "conflict of events".

The problem as far as I can tell is that focus() is triggered either by tabbing over to a link or a mousedown() on an element that can receive focus. So, every time a link is clicked on, it's getting focus. But, a click() event isn't complete until the mouse is released. So the effect that I was seeing in Firefox and IE was a result of a slight delay between mousedown() and mouseup(). I tried just swapping out the .click() event handling in my code to mousedown(), and since it's just a single event that I was watching out for I decided to integrate that into my hoverState() function. I ended up with this:

function hoverState(e) {
    var index = $(e.target.parentNode).prevAll().length;
    if (e.type == 'mousedown') {
        statePad=Math.pow(10,(4-index))*2;
        $(".active").removeClass('active');
        $("#"+ e.target.id).addClass('active');
}
else {
   var sign = (e.type === 'mouseenter' || 
                 e.type === 'focus')? 1 : -1;
 var placeholder=Math.pow(10,4-index);
    if (statePad != placeholder*2)
        statePad += (placeholder * sign);
 $('h1').text(statePad + " " + e.type);
    }
    stateChanger(statePad);
}

$("#navbar a").bind("mouseenter mouseleave focus blur mousedown", hoverState);

However, this caused some bizarre behavior that screwed up the statePad variable. I reverted back to the code that Russ Cam provided me, and started rethinking things. I tried it out in Opera, which I hadn't done yet, and it worked fine. I tried it in Safari and Chrome, and they worked fine as usual. I tried it in Firefox, just trying to get a handle on what makes it different, and...it was working!

I looked back at my code, and it turns out that I was still binding the hoverState function to the mousedown() event. I'm not exactly sure why this works, but it does. It fixes the problem in IE, as well. Strangle, it causes a new issue in Chrome, it's so minor that I'm not even going to worry about.

I don't think I would have been able to resolve this without Russ' help, so I want to thank him again for all of his help in this.

OTHER TIPS

There's a lot of unnecessary lengthening of the scope chain in your code, and a longer scope chain will take longer to resolve. It could be shortened to the following

$("navbar a").click(blah) 
             .hover(foo,bar)
             .focus(foo)
             .blur(bar);

Hopefully this should result in less of a noticeable lag. If you still see noticeable lag after making this change, please post the code for the event handler functions as there may be improvements that can be made to that code too.

EDIT:

In response to your comment, you could get the index in the function using the passed in event object's target property, which will be the element that the event was raised on. So, to get the index of an <a> element in all the <a> elements in the <ul> with id navbar, we can use the fact that each <a> is contained within a <li>, therefore the index in each case will be the same. With this is mind, event.target will be the <a> element that the click event is raised on, event.target.parentNode will be the parent element of <a> which is the <li>

To get the index, you could use

function hoverState(e) { 
    // get the index of the <a> element, which will be the same
    // as the index of the <li> that contains it in the <ul>
    //
    var index = $(e.target.parentNode).prevAll().length; 
    //
    // get the sign
    var sign = (e.type === 'mouseenter' || e.type === 'focus')? 1 : -1;
} 

This will remove the need for anonymous function event handlers wrapping hoverState.

Here's some reworked code

var statePad=0;

// the purpose of this function is to translate 
// the current state of the menu into a different 
// graphical representation of the menu state.
//
function stateChanger(ourStatePad) {

    var tempVar=ourStatePad;
    var tempArray = [0,0,0,0,0];
    for (var x=0;x < 5;x++) {
        tempArray[x]=tempVar % 10;
        tempVar=(tempVar-tempArray[x])/10;
    }
    // Calculate the proper position in our CSS sprite, 
    // based on the each link's state, as well as it's neighbors'
    //
    var arrayIndex=4;
    while (arrayIndex--) {

        $("#rightpostheader div.block").eq(4 - arrayIndex)
            .css(
                "backgroundPosition",
                //x-position
                ((4 - arrayIndex) * -100) + "px " + 
                //y-position
                (tempArray[arrayIndex] + ((3 * tempArray[(arrayIndex) + 1]) * -30))) + "px";
    }

}


function hoverState(e) {
    var index = $(e.target.parentNode).prevAll().length;
    var sign = (e.type === 'mouseenter' || 
                e.type === 'focus')? 1 : -1;
    var placeholder=Math.pow(10,4-index);

    if (statePad != placeholder*2)
        statePad += (placeholder * sign);
    stateChanger(statePad);
}

$("#navbar a")
    .click(function(e) {
        // might be able to rework this into using hoverState too
        var $this = $(e.target);

        // get the index of the parent <li>
        var index= $this.parent().prevAll().length;

        statePad=Math.pow(10,(4-index))*2;

        stateChanger(statePad);

        $("#navbar a").removeClass('active');
        $this.addClass('active');
    })
    .bind("mouseenter mouseleave focus blur", hoverState);  
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top