Expanding hover area to outside element
You 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
.
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
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>