扩大悬停区域至元素外部

12

我有一个下拉菜单,其中子菜单放置在不同的元素上。所以基本上当鼠标离开菜单项时,子菜单会立即关闭,因为子菜单不是其子元素。

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>

问题是如何扩展悬停区域,使得当光标指向子菜单时,它会忽略hoverOut的动作。

注意:不要告诉我将子菜单放在菜单项内部,我已经知道那样可以实现。这是为了处理需要将子菜单放在菜单项外部的情况。

https://jsfiddle.net/yans_fied/6wj0of90/


你可以使用 jquery 来实现这个。 - mmativ
你需要让被悬停的元素变大,或者将子菜单放在其中。 - evolutionxbox
如果您的子菜单需要在外部,那么当用户指向子菜单链接时,如何打开子菜单呢?因此,子菜单需要在主菜单内部。 - Samir
@Samir,你看过我的代码片段了吗?基本上它就像一个选项卡 UI,它保存选项卡 ID 以打开选项卡内容。 - Ariona Rian
我明白你的问题,关于你的问题有一个解决方案。只需增加具有menu-item类的li的底部填充即可。确保sub-menuli相互附着在一起。但问题是,如果用户想要点击子菜单,在这种情况下用户必须退出父级li,这样sub-menu就会自动隐藏。你想要增加多少父级li的底部填充?可以吗? - Samir
6个回答

6
你可以将 sub-menu 直接放在 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>

另一种方法是检查.menu-item.sub-menuhover状态。这里需要设置一个小超时时间,以防止它关闭得太早。

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>


@eisnehr 我已经知道了,但这是为了不同的布局,子菜单需要放在菜单项外面。 :) - Ariona Rian
我已经用第二个例子扩展了我的答案 @ArionaRian - eisbehr
嗨 @eisnehr,你能帮我检查一下这个链接吗?https://jsfiddle.net/yans_fied/6wj0of90/ 看起来它需要更多的解决方法。 - Ariona Rian

3
你可以添加 <\p>
.sub-menu::before{
     content:'';
     height: <height of menu item>
     width: 100%;
     position:absolute;
     bottom:100%;
}

hoverOut放置在.sub-menu上。


2

以下是一个示例,其中:

  1. 为子菜单添加了伪元素,以提供重叠区域进行悬停。仅出于演示目的,它是黄色的。
  2. 鼠标从菜单和子菜单中移开时,仅设置一个变量。子菜单在单独的函数中隐藏,该函数评估变量。需要稍微延迟一下,以允许从一个变量到另一个变量的更改。

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>


2

今天我在你的代码上玩了一会儿,从你的Fiddle开始,而不是部分片段...

你离成功很近了...
但问题在于你有两个不同的父元素类需要处理(即:需要绑定事件处理程序以处理它们)...并且需要进行不同的处理。

当你将鼠标从打开子菜单的元素移动到另一个应该保持打开状态的元素时,某些事件不应该触发。只有当鼠标没有快速进入另一个menu___itemdropdown-menu__content时,才会发生mouseout事件。

mouseentermouseout的触发速度相当...比人类鼠标移动更快。

这里使用一个小的100ms延迟非常有用。

在离开这些元素时,使用setTimeout()dropdown-holder设置为display:none,并在进入时使用clearTimeout

$(".menu__item").hover(
  function() {
    $(".dropdown-holder").css({"display":"block"});
    displaySubMenu( $(this) );
    clearTimeout(NavDelay);
  },
  function(){
    setNavDelay();
  });

$(".dropdown-menu__content").hover(
  function() {
    clearTimeout(NavDelay);
  },
  function(){
    setNavDelay();
  });

setTimeout函数很简单:

function setNavDelay(){
  NavDelay = setTimeout(function(){
        $(".dropdown-holder").css({"display":"none"});
    },100);
}

这里是子菜单显示功能,没有太多修改:

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"
  });
}

为确保正确的内容显示,通过menu__item hovermouseenter处理程序将menuHoveredID变量传递给函数。
您的onload声明:
var dr = $(".dropdown__content"),
    drBg = $(".dropdown__bg"),
    drBgBtm = $(".dropdown__bg-bottom"),
    drArr = $(".dropdown__arrow"),
    drMenu = $(".dropdown-menu__content"),
    drCnt = $(".dropdown__content"),

    menuHoveredID ="",
    NavDelay;

我去掉了不必要的部分并添加了两个变量...
如果你注意到,我还纠正了分号/逗号... ;)

这里是可工作的CodePen


谢谢你详细的回答,但在你发帖之前我已经解决了这个问题:D,setTimeout确实适用于这种情况。 - Ariona Rian
很好。我忘了提到另一件事情,我使用了display:none而不是opacity:0,因为子菜单中的链接即使没有显示仍然可以被点击(在Chrome浏览器中,可能不是所有浏览器都是如此)...如果你寻找它,你会看到手指指针。;) - Louys Patrice Bessette
可以通过 pointer-events: none 或切换 z-index 来解决这个问题 :) - Ariona Rian

1
如果你将你的第一行js代码改为:var menuItem = $(".menu-item, .sub-menu");,然后在.menu-list css中添加top: 3em;(与.menu div提供微小的重叠并消除闪烁),那么一切都应该没问题了。

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>


@ArionaRian,我试了一下新的fiddle https://jsfiddle.net/manoeuvres/73452ken/,由于三角形的关系,菜单触发有时不可靠,但你可能希望保留下一次迭代中的其他微调。 - Sam0

-4

好的,我已经找到了解决方案。感谢你们所有人给出的建议,但是我不能接受你们的答案,因为解决方案需要更多的解决方法。

所以,基本上我创建了两个函数,分别是startCloseTimeout()stopCloseTimout(),并将其绑定在menu-itemsubmenu上。

这就是函数本身:

var startCloseTimeout = function (){
    closeDropdownTimeout = setTimeout( () => closeDropdown() , 50 );
},

stopCloseTimeout   = function () {
    clearTimeout( closeDropdownTimeout );
};

这是我如何绑定鼠标事件的代码:

//- 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 );

} );

那么,这段代码是如何工作的呢?

通过创建关闭超时处理程序,我们可以控制下拉菜单何时应该关闭或不关闭。

当鼠标进入菜单项时,会发生以下情况: 1. 停止当前运行的closeDropdownTimout 2. 打开相关的下拉菜单

当鼠标离开菜单项时,它会启动closeDropdownTimout

但是下拉菜单如何保持打开状态呢?由于我们在下拉菜单上设置了相同的操作,closeDropdownTimout将被清除并取消关闭操作。

要获取完整的源代码,请访问codepen http://codepen.io/ariona/pen/pENkXW

谢谢。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接