I have a dropdown menu, which the sub-menu placed on different element. So basically when the mouse leave the menu item, the sub-menu get closed immediately because the sub-menu is not the child.
var menuItem = $(".menu-item");
menuItem.hover(hoverIn, hoverOut);
function hoverIn() {
var mnItemMeta = $(this)[0].getBoundingClientRect();
$(".sub-menu").css({
opacity: 1,
left: mnItemMeta.left
})
}
function hoverOut() {
$(".sub-menu").css({
opacity: 0
})
}
html,body{background-color: #efefef;}
.menu {
list-style: none;
padding-left: 0;
display: flex;
justify-content: center;
}
a {
display: block;
padding: 10px 20px;
text-decoration: none;
color: inherit;
}
.sub-menu {
opacity: 0;
background-color: white;
position: absolute;
transition: .2s ease;
}
.sub-menu-list {
list-style: none;
padding-left: 0;
}
<script src=".11.1/jquery.min.js"></script>
<ul class="menu">
<li class="menu-item"><a href="#">Menu Item</a>
</li>
</ul>
<div class="sub-menu">
<ul class="sub-menu-list">
<li><a href="#">Sub Menu 1</a>
</li>
<li><a href="#">Sub Menu 2</a>
</li>
<li><a href="#">Sub Menu 3</a>
</li>
<li><a href="#">Sub Menu 4</a>
</li>
</ul>
</div>
I have a dropdown menu, which the sub-menu placed on different element. So basically when the mouse leave the menu item, the sub-menu get closed immediately because the sub-menu is not the child.
var menuItem = $(".menu-item");
menuItem.hover(hoverIn, hoverOut);
function hoverIn() {
var mnItemMeta = $(this)[0].getBoundingClientRect();
$(".sub-menu").css({
opacity: 1,
left: mnItemMeta.left
})
}
function hoverOut() {
$(".sub-menu").css({
opacity: 0
})
}
html,body{background-color: #efefef;}
.menu {
list-style: none;
padding-left: 0;
display: flex;
justify-content: center;
}
a {
display: block;
padding: 10px 20px;
text-decoration: none;
color: inherit;
}
.sub-menu {
opacity: 0;
background-color: white;
position: absolute;
transition: .2s ease;
}
.sub-menu-list {
list-style: none;
padding-left: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<ul class="menu">
<li class="menu-item"><a href="#">Menu Item</a>
</li>
</ul>
<div class="sub-menu">
<ul class="sub-menu-list">
<li><a href="#">Sub Menu 1</a>
</li>
<li><a href="#">Sub Menu 2</a>
</li>
<li><a href="#">Sub Menu 3</a>
</li>
<li><a href="#">Sub Menu 4</a>
</li>
</ul>
</div>
https://jsfiddle.net/yans_fied/6wj0of90/
The question is how to extend the hover area, so when the cursor point into sub-menu it ignore the hoverOut
action.
NOTE: don't tell me to place the sub-menu inside the menu-item, I already how that worked. It's for different case that need the sub-menu to be placed outside the menu-item.
Share Improve this question edited Jan 13, 2021 at 17:39 Brian Tompsett - 汤莱恩 5,88372 gold badges61 silver badges133 bronze badges asked Sep 21, 2016 at 9:10 Ariona RianAriona Rian 9,4373 gold badges25 silver badges36 bronze badges 5 |6 Answers
Reset to default 6You could just place the sub-menu
in the menu-item
.
var menuItem = $(".menu-item");
menuItem.hover(hoverIn, hoverOut);
function hoverIn() {
var mnItemMeta = $(this)[0].getBoundingClientRect();
$(".sub-menu").css({
opacity: 1,
left: mnItemMeta.left
})
}
function hoverOut() {
$(".sub-menu").css({
opacity: 0
})
}
html, body {
background-color: #efefef;
}
.menu {
list-style: none;
padding-left: 0;
display: flex;
justify-content: center;
}
a {
display: block;
padding: 10px 20px;
text-decoration: none;
color: inherit;
}
.sub-menu {
opacity: 0;
background-color: white;
position: absolute;
transition: .2s ease;
}
.sub-menu-list {
list-style: none;
padding-left: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<ul class="menu">
<li class="menu-item"><a href="#">Menu Item</a>
<div class="sub-menu">
<ul class="sub-menu-list">
<li><a href="#">Sub Menu 1</a></li>
<li><a href="#">Sub Menu 2</a></li>
<li><a href="#">Sub Menu 3</a></li>
<li><a href="#">Sub Menu 4</a></li>
</ul>
</div>
</li>
</ul>
Another way would be to check the hover
state of .menu-item
and .sub-menu
. You need to work with a little timeout here, to prevent it from closing to early.
var timeout,
hovered = false,
menuItem = $(".menu-item, .sub-menu").hover(hoverIn, hoverOut);;
function hoverIn() {
hovered = true;
var mnItemMeta = this.getBoundingClientRect();
$(".sub-menu").show().css({
opacity: 1,
left: mnItemMeta.left,
});
}
function hoverOut() {
hovered = false;
clearTimeout(timeout);
timeout = setTimeout(function() {
if (!hovered) {
$(".sub-menu").css({
opacity: 0,
}).hide()
}
}, 100);
}
html, body {
background-color: #efefef;
}
.menu {
list-style: none;
padding-left: 0;
display: flex;
justify-content: center;
}
a {
display: block;
padding: 10px 20px;
text-decoration: none;
color: inherit;
}
.sub-menu {
opacity: 0;
background-color: white;
position: absolute;
transition: .2s ease;
}
.sub-menu-list {
list-style: none;
padding-left: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<ul class="menu">
<li class="menu-item"><a href="#">Menu Item</a></li>
</ul>
<div class="sub-menu">
<ul class="sub-menu-list">
<li><a href="#">Sub Menu 1</a></li>
<li><a href="#">Sub Menu 2</a></li>
<li><a href="#">Sub Menu 3</a></li>
<li><a href="#">Sub Menu 4</a></li>
</ul>
</div>
You could add
.sub-menu::before{
content:'';
height: <height of menu item>
width: 100%;
position:absolute;
bottom:100%;
}
and place the hoverOut
on the .sub-menu
.
Here is an example where
- a pseudo element is added to the submenu to provide an overlapping area for the hover. It is yellow only for demo purposes.
- the hover out of both the menu and the submenu only set a variable. The submenu is hidden in a separate function, wich evaluates the variables. A slight timeout is needed to allow for the change from one to the other.
var menuItem = $(".menu-item");
var submenuItem = $(".sub-menu");
var hoverMenu = false;
var hoverSubmenu = false;
menuItem.hover(hoverIn, hoverOut);
function hoverIn() {
hoverMenu = true;
var mnItemMeta = $(this)[0].getBoundingClientRect();
$(".sub-menu").css({
opacity: 1,
left: mnItemMeta.left
})
}
function hoverOut() {
hoverMenu = false;
setTimeout (hide, 10);
}
submenuItem.hover(hoverSmIn, hoverSmOut);
function hoverSmIn() {
hoverSubmenu = true;
}
function hoverSmOut() {
hoverSubmenu = false;
setTimeout (hide, 10);
}
function hide() {
if (hoverMenu == false && hoverSubmenu == false) {
$(".sub-menu").css({
opacity: 0
})
}
}
html,body{background-color: #efefef;}
.menu {
list-style: none;
padding-left: 0;
display: flex;
justify-content: center;
}
a {
display: block;
padding: 10px 20px;
text-decoration: none;
color: inherit;
}
.sub-menu {
opacity: 0;
background-color: white;
position: absolute;
transition: .2s ease;
}
.sub-menu:before {
content: "";
position: absolute;
width: 100%;
left: 0px;
bottom: 100%;
height: 26px;
background-color: yellow;
}
.sub-menu-list {
list-style: none;
padding-left: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<ul class="menu">
<li class="menu-item"><a href="#">Menu Item</a>
</li>
</ul>
<div class="sub-menu">
<ul class="sub-menu-list">
<li><a href="#">Sub Menu 1</a>
</li>
<li><a href="#">Sub Menu 2</a>
</li>
<li><a href="#">Sub Menu 3</a>
</li>
<li><a href="#">Sub Menu 4</a>
</li>
</ul>
</div>
I played a while on your script today, starting with your Fiddle, not the partial snippet...
You were so close...
But the thing is that you have two different parent element classes to handle (read: event handlers to bind to them)... And to handle differently.
When you move the mouse from an element that opened the sub-menu to another that should keep it opened, some events should not trigger. A mouseout
event should occur only if the mouse do not enter another menu___item
or a dropdown-menu__content
"fast enought".
mouseenter
and mouseout
are quite fast on the trigger... Faster than human mouse move.
A small 100ms delay is usefull here.
A setTimeout()
to set dropdown-holder
to display:none
on leaving these elements and a clearTimeout
on entering.
$(".menu__item").hover(
function() {
$(".dropdown-holder").css({"display":"block"});
displaySubMenu( $(this) );
clearTimeout(NavDelay);
},
function(){
setNavDelay();
});
$(".dropdown-menu__content").hover(
function() {
clearTimeout(NavDelay);
},
function(){
setNavDelay();
});
The setTimout function is simple:
function setNavDelay(){
NavDelay = setTimeout(function(){
$(".dropdown-holder").css({"display":"none"});
},100);
}
And here is the sub-menu display function, which was not modified that much:
function displaySubMenu(element){
var itemMeta = element[0].getBoundingClientRect();
//console.log( itemMeta );
var subID = element.data('sub');
console.log(subID);
var subCnt = $(subID).find(".dropdown-menu__content").css({"display":"block"});
var subMeta = subCnt[0].getBoundingClientRect();
//console.log( subMeta );
var subCntBtm = subCnt.find(".bottom-section");
menuHoveredID = subID; // Let's Keep this info in memory in a var that has global scope
$(drBg).css({
"display":"block",
"left": itemMeta.left - ((subMeta.width / 2) - itemMeta.width / 2),
"width": subMeta.width,
"height": subMeta.height
});
$(drBgBtm).css({
"top": subCntBtm.position().top
});
$(drArr).css({
"display":"block",
"left": itemMeta.left + itemMeta.width / 2 - 10
});
$(drCnt).css({
"display":"block",
"left": itemMeta.left - ((subMeta.width / 2) - itemMeta.width / 2),
"width": subMeta.width,
"height": subMeta.height
});
// Ensure the right content is displayed
$(".dropdown-menu__content").css({
"display":"none"
});
$(menuHoveredID).find(".dropdown-menu__content").css({
"display":"block"
});
}
To ensure the right content is displayed, the menuHoveredID
variable is brougth to the function via the mouseenter
handler of menu__item
hover
.
Your onload declarations:
var dr = $(".dropdown__content"),
drBg = $(".dropdown__bg"),
drBgBtm = $(".dropdown__bg-bottom"),
drArr = $(".dropdown__arrow"),
drMenu = $(".dropdown-menu__content"),
drCnt = $(".dropdown__content"),
menuHoveredID ="",
NavDelay;
I Stripped off the unnessary and added two vars...
If you notice, I also corrected semicolon / coma... ;)
Working CodePen here
if you change your first js line to: var menuItem = $(".menu-item, .sub-menu");
and then add top: 3em;
to the .menu-list
css (to provide a tiny overlap with the .menu
div and remove the flicker)then all should be good.
var menuItem = $(".menu-item, .sub-menu");
menuItem.hover(hoverIn, hoverOut);
function hoverIn() {
var mnItemMeta = $(this)[0].getBoundingClientRect();
$(".sub-menu").css({
opacity: 1,
left: mnItemMeta.left
})
}
function hoverOut() {
$(".sub-menu").css({
opacity: 0
})
}
html,body{background-color: #efefef;}
.menu {
list-style: none;
padding-left: 0;
display: flex;
justify-content: center;
}
a {
display: block;
padding: 10px 20px;
text-decoration: none;
color: inherit;
}
.sub-menu {
opacity: 0;
background-color: white;
position: absolute;
transition: .2s ease;
top: 3em;
}
.sub-menu-list {
list-style: none;
padding-left: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<ul class="menu">
<li class="menu-item"><a href="#">Menu Item</a>
</li>
</ul>
<div class="sub-menu">
<ul class="sub-menu-list">
<li><a href="#">Sub Menu 1</a>
</li>
<li><a href="#">Sub Menu 2</a>
</li>
<li><a href="#">Sub Menu 3</a>
</li>
<li><a href="#">Sub Menu 4</a>
</li>
</ul>
</div>
Okay, i have figured the solution. Thanks to all of you for giving some advice, but I can't accept the answers from you guys since the solution need more workaround.
So, basically a i created two function, which is startCloseTimeout()
and stopCloseTimout()
, and bind it on both menu-item
and submenu
.
Here is the function itself:
var startCloseTimeout = function (){
closeDropdownTimeout = setTimeout( () => closeDropdown() , 50 );
},
stopCloseTimeout = function () {
clearTimeout( closeDropdownTimeout );
};
And here is how i bind to mouse events:
//- Binding mouse event to each menu items
menuItems.forEach( el => {
//- mouse enter event
el.addEventListener( 'mouseenter', function() {
stopCloseTimeout();
openDropdown( this );
}, false );
//- mouse leave event
el.addEventListener( 'mouseleave', () => startCloseTimeout(), false);
} );
//- Binding mouse event to each sub menus
menuSubs.forEach( el => {
el.addEventListener( 'mouseenter', () => stopCloseTimeout(), false );
el.addEventListener( 'mouseleave', () => startCloseTimeout(), false );
} );
So, how this code work?
By creating close timeout handler we can control when the dropdown should close or not.
When the mouse is entering the menu-item this is what happen:
1. Stop current running closeDropdownTimout
2. Open related dropdown menu
when the mouse is leaving the menu-item it start the closeDropdownTimout
.
But how the dropdown menu still open? Since we set the same action on dropdown menu, the closeDropdownTimout
well be cleared and canceling the close action.
For full source code you can check it out on codepen http://codepen.io/ariona/pen/pENkXW
Thanks.
jquery
for that, – mmativ Commented Sep 21, 2016 at 9:11li
having classmenu-item
. Make sure thesub-menu
and the li should attached to each other. But the problem is, if user wants to click on the sub menu, at this time the user has to come out of the parentli
, and hence thesub-menu
will hide automatically. How much you will increase your parentli
bottom paddnig? Can you? – Samir Commented Sep 21, 2016 at 9:37