jQuery hover dependent on two elements
Asked Answered
I

3

7

I have main navigation and sub navigation that for reasons of design are in separate DIVs. I would like to show the appropriate sub-nav when a main nav item is hovered, which I can do, but I also want to keep the sub-nav open if the user moves their mouse outside of the main nav item and into the sub-nav area. The last part is the place where I'm getting stuck.

I'm thinking on the hover out I need to do something with setTimeout() and an IF statement, but I have not been able to make any progress in that area. Is that even an approach worth trying?

HTML:

<div id="mnav">
 <ul id="buttons">
  <li class="one"><a href="#">Main 1</a></li>
  <li class="two"><a href="#">Main 2</a></li>
  <li class="three"><a href="#">Main 3</a></li>
  <li class="four nav-dark"><a href="#">Main 4</a></li>
 </ul>
</div> <!-- /mnav -->

<div id="snav">
 <ul class="snav-one">
    <li><a href="#">Sub 1.1</a></li>
    <li><a href="#">Sub 1.2</a></li>
    <li><a href="#">Sub 1.3</a></li>
    <li><a href="#">Sub 1.4</a></li>
    <li><a href="#">Sub 1.5</a></li>
    <li><a href="#">Sub 1.6</a></li>
 </ul>
 <ul class="snav-two">
    <li><a href="#">Sub 2.1</a></li>
    <li><a href="#">Sub 2.2</a></li>
 </ul>
</div> <!-- /snav -->

JS:

$(document).ready(function() {
 $("#buttons li.one, #buttons li.two").hover(function(){
  var subnav = 'ul.snav-' + $(this).attr('class');

  $("#snav").slideDown('fast').addClass("open").find(subnav).show();

 }, function(e){
  var subnav = 'ul.snav-' + $(this).attr('class');

  $("#snav").slideUp('fast').removeClass("open").find(subnav).hide();
 });
});
Inefficient answered 25/5, 2011 at 23:11 Comment(0)
T
8

For Mouse-menu ergonomics you want a small delay while mousing from main to sub menus, so the the sub menu does not close before the mouse gets there. (As the question says.)

But, you also need a delay before menus open -- both to avoid annoying "flyover" activation, and to reduce the common occurrence of accidentally switching from sub1 to sub2 while moving off the main menu.

So, the question code needs:

  1. hover over the sub-menu ul elements.
  2. stop to halt running animations if mouse selection changes.
  3. A resettable timer controlling both open and close.

See the demo at jsFiddle.

Putting it all together:

$("#buttons li.one, #buttons li.two").hover ( function () { MenuOpenCloseErgoTimer (
        100,
        function (node) {
            var subnav = 'ul.snav-' + $(node).attr ('class');
            $("#snav ul").hide ();
            $("#snav").stop (true, true).slideDown ('fast').addClass ("open").find (subnav).show ();
        },
        this
    ); },
    function () { MenuOpenCloseErgoTimer (
        200,
        function () {
            $("#snav").stop (true, true).slideUp ('fast').removeClass ("open").find ('ul').hide ();
        }
    ); }
);

$("div#snav ul").hover ( function () { MenuOpenCloseErgoTimer (
        0,
        function () {
            $("#snav").stop (true, true).slideDown ('fast').addClass ("open");
            $(this).show ();
        }
    ); },
    function () { MenuOpenCloseErgoTimer (
        200,
        function () {
            $("#snav").stop (true, true).slideUp ('fast').removeClass ("open");
            $("#snav ul").hide ();
        }
    ); }
);

function MenuOpenCloseErgoTimer (dDelay, fActionFunction, node) {
    if (typeof this.delayTimer == "number") {
        clearTimeout (this.delayTimer);
        this.delayTimer = '';
    }
    if (node)
        this.delayTimer     = setTimeout (function() { fActionFunction (node); }, dDelay);
    else
        this.delayTimer     = setTimeout (function() { fActionFunction (); }, dDelay);
}



Note the extra operations required on #snav ul, to cleanup after interrupted swaps between sub-menus.

Tailing answered 26/5, 2011 at 2:19 Comment(4)
That worked perfectly! Thank you so much for taking the time to put that together you're really helping me out AND teaching me a ton.Inefficient
I don't want to stretch it, but would you have any thoughts on refactoring to allow a subnav to be open on a page by default?Inefficient
That can get tricky, best to open a new question. State whether you want certain submenu(s) to remain open even if the user opens a closed one. The basic idea is that the hover-out functions restore whatever default state. Best to use CSS classes rather than hide and show.Tailing
I've posted another question here. The logic you've described seems so easy until I run into the issue of how do I know/save the initial state the menu is in.Inefficient
S
4

Instead of using the .hover() method, try using separate event handlers for mouseenter and mouseleave. The mouseenter would only be attached to the mnav buttons, while the mouseleave would be attached to both the mnav button and the snav div. On your mouseleave functions you'll probably have to add a small timeout and check to see if they went from one element to another.

Try something like this:

<script>
    var timer;
    $(document).ready(function() {
        $("#mnav").mouseenter(function() {
            $("#snav").slideDown();
        });

        $("#mnav, #snav").mouseleave(function() {
            timer=setTimeout("$('#snav').slideUp();",50);
        });

        $("#mnav, #snav").mouseenter(function() {
            clearTimeout(timer);
        });
    });
</script>
Skywriting answered 26/5, 2011 at 0:34 Comment(3)
Can you give me an example of the mouseleave event handler, even if it's pseudo code?Inefficient
I try this but tell me "TypeError: $ is not a function" !!! $ not detect in setTimeout !!!Secretion
@T Nguyen Aaaaaa, you're the man. It works perfectly. Thank you, thank you, thank you very much.Karriekarry
A
1

See example →

The following should work for you, I made several changes:

  • used the .stop() method to halt the animation on entering the subnav
  • moved the slides to the subnav ul's instead of the container so the above would work
  • some other things

See below:

$("#buttons li.one, #buttons li.two").hover(function() {
    var subnav = 'ul.snav-' + $(this).attr('class');

    $("#snav").find('ul').slideUp('fast');
    $("#snav").addClass("open").find(subnav).stop(true, true).slideDown('fast');
}, function(e) {
    var subnav = 'ul.snav-' + $(this).attr('class');

    $("#snav").removeClass("open").find(subnav).slideUp('fast');
});

$('#snav').bind('mouseenter', function(e) {
    $(this).find('ul').stop(true, false);
}).bind('mouseleave', function(e) {
    $(this).find('ul').stop(true, true).slideUp('fast');
});

See example →

Aconcagua answered 26/5, 2011 at 0:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.