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

  1. a pseudo element is added to the submenu to provide an overlapping area for the hover. It is yellow only for demo purposes.
  2. 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>