Расширение области наведения на внешний элемент

У меня есть раскрывающееся меню, в котором подменю размещено на другом элементе. Таким образом, в основном, когда мышь покидает элемент меню, подменю немедленно закрывается, потому что подменю не является дочерним.

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>

Ответ 1

Хорошо, я понял решение. Спасибо всем вам за советы, но я не могу принять ответы от вас, ребята, потому что решение нуждается в более обходном пути.

Итак, в основном я создал две функции, которые являются startCloseTimeout() и stopCloseTimout(), и привязываю их как к menu-item, так и к submenu.

Вот сама функция:

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

Спасибо.

Ответ 2

Вы можете просто поместить 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>

Ответ 3

Вы можете добавить

.sub-menu::before{
     content:'';
     height: <height of menu item>
     width: 100%;
     position:absolute;
     bottom:100%;
}

и поместите hoverOut на .sub-menu.

Ответ 4

если вы измените первую строку js на: var menuItem = $(".menu-item, .sub-menu");, а затем добавьте top: 3em; в .menu-list css (чтобы обеспечить крошечное совпадение с div .menu и удалить мерцание), тогда все должно быть хорошо.

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>

Ответ 5

Вот пример, где

  • псевдо-элемент добавляется в подменю, чтобы обеспечить перекрывающуюся область для наведения. Это желтый цвет только для демонстрационных целей.
  • зависание из меню и подменю устанавливает только переменную. Подменю скрыто в отдельной функции, которая оценивает переменные. Требуется небольшой тайм-аут, позволяющий перейти от одного к другому.

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>

Ответ 6

Я играл некоторое время на вашем script сегодня, начиная с вашего Fiddle, а не частичного фрагмента...

Ты был так близок...
Но дело в том, что у вас есть два разных класса родительских элементов для обработки (чтение: обработчики событий для привязки к ним)... И для обработки по-разному.

Когда вы перемещаете мышь из элемента, который открыл подменю, чтобы другое открывало, некоторые события не должны запускаться. Событие mouseout должно происходить только в том случае, если мышь не вводит другой menu___item или dropdown-menu__content "достаточно быстро".

mouseenter и mouseout являются довольно быстрыми на триггере... быстрее, чем движение мыши.

Здесь нужна небольшая задержка в 100 мкс.

A setTimeout(), чтобы установить dropdown-holder в display:none при выходе из этих элементов и a 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();
  });

Функция setTimout проста:

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

Чтобы обеспечить отображение правильного содержимого, переменная menuHoveredID передается функции с помощью обработчика mouseenter menu__item hover.

Ваши объявления onload:

var dr = $(".dropdown__content"),
    drBg = $(".dropdown__bg"),
    drBgBtm = $(".dropdown__bg-bottom"),
    drArr = $(".dropdown__arrow"),
    drMenu = $(".dropdown-menu__content"),
    drCnt = $(".dropdown__content"),

    menuHoveredID ="",
    NavDelay;

Я убрал unnessary и добавил два vars...
Если вы заметили, я также исправил точку с запятой/кома...;)

Рабочий кодPen здесь