cssangularversion-control

Issue with MouseEnter/MouseLeave


I'm trying to build a notification dropdown that appears when I hover over the bell icon. I’ve styled the bell and the dropdown with flashy neon effects, and I even added a small invisible .notification-hover-bridge element between them to prevent the dropdown from disappearing when the mouse moves from the bell to the dropdown.

But here’s the problem:
The dropdown still closes too early, because there's a small gap between the bell and the dropdown. When I move my mouse from the bell to the dropdown, even if I go quickly and carefully, it triggers a mouseleave event before I can reach the dropdown — so it vanishes.

What I want is a seamless hover area that includes:
the bell icon,
the space between,
and the dropdown itself.

So I need a fix where the mouse can travel across that invisible bridge without the dropdown disappearing — basically, one unified hover zone that keeps the dropdown visible as long as I’m somewhere in that area.

I am using angular/scss:

<div class="notification-wrapper" 
         (mouseenter)="showDropdown = true"
         (mouseleave)="showDropdown = false">

      <!-- Notification Bell -->
      <div class="notification-bell" [attr.data-count]="getUnreadCount()">
        🔔 {{ getUnreadCount() }}
      </div>

      <!-- Hover Bridge (optional, but safer with nested timing issues) -->
      <div class="notification-hover-bridge"></div>

      <!-- Dropdown -->
      <ul class="notifications-dropdown" *ngIf="showDropdown">
        <li class="mark-all-container" *ngIf="notifications.length > 0">
          <span>Notifications</span>
          <button (click)="markAllAsRead()">Mark All as Read</button>
        </li>
        <li *ngFor="let n of notifications">
          <a *ngIf="n.link" [routerLink]="n.link" (click)="markAsRead(n.id)">
            {{ n.message }}
          </a>
          <span *ngIf="!n.link">{{ n.message }}</span>
          <button (click)="markAsRead(n.id)">Mark as Read</button>
        </li>
        <li *ngIf="notifications.length === 0">
          No notifications yet!
        </li>
      </ul>
    </div>
    <button *ngIf="loggedIn" (click)="goToMyShit()" class="btn btn-warning">💩 My Shit</button>
  </div>

and

.notification-wrapper {
  position: relative;
  display: inline-block;
  z-index: 1000; // ensures it's above other elements if needed
}

.notification-bell {
  position: relative;
  display: inline-block;
  font-size: 1.5rem;
  color: #ff0;
  cursor: pointer;
  text-shadow: 0 0 10px #ff0, 0 0 20px #ff0;
  transition: transform 0.2s ease-in-out;

  &:hover {
    transform: scale(1.1);
  }

  &::after {
    content: attr(data-count);
    position: absolute;
    top: -8px;
    right: -12px;
    background-color: #f00;
    color: white;
    font-size: 0.7rem;
    font-weight: bold;
    padding: 4px 6px;
    border-radius: 50%;
    box-shadow: 0 0 10px #f00, 0 0 20px #f00;
    z-index: 10;
  }
  
  .notification-hover-bridge {
    position: absolute;
    pointer-events: auto;
    top: 100%;
    left: 0;
    width: 100%;
    height: 12px; // tweak as needed
    z-index: 999;
  }

  .notifications-dropdown {
    display: none;
    position: absolute;
    top: calc(100% + 2px);
    left: 50%;
    transform: translate(-50%);
    width: 300px;
    max-height: 400px;
    overflow-y: auto;
    background: #1a1a1a;
    border: 2px solid #ff00ff;
    border-radius: 10px;
    padding: 0px;
    margin-top: 0px;
    list-style: none;
    z-index: 1001;
    box-shadow: 0 0 20px #ff00ff, 0 0 30px #ff00ff;

    .mark-all-container {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 12px 16px;
      background: #2a2a2a;
      border-bottom: 2px solid #ff00ff;
      color: #ff0;
      font-weight: bold;
      font-size: 1rem;
      text-shadow: 0 0 10px #ff0;

      button {
        background: none;
        border: 1px solid #ff0;
        color: #ff0;
        padding: 4px 10px;
        font-size: 0.75rem;
        border-radius: 5px;
        text-shadow: 0 0 5px #ff0;
        box-shadow: 0 0 10px #ff0;
        cursor: pointer;
        transition: all 0.3s ease;

        &:hover {
          background-color: #ff0;
          color: #121212;
          box-shadow: 0 0 20px #ff0, 0 0 40px #ff0;
        }
      }
    }

    li {
      padding: 10px 16px;
      border-bottom: 1px solid #333;
      display: flex;
      justify-content: space-between;
      align-items: center;
      font-size: 0.95rem;
      color: #fff;
      text-shadow: 0 0 5px #0f0;
      transition: background-color 0.3s;

      &:hover {
        background-color: #2a2a2a;
      }

      button {
        background: none;
        border: 1px solid #0ff;
        padding: 4px 8px;
        color: #0ff;
        border-radius: 6px;
        font-size: 0.75rem;
        cursor: pointer;
        text-shadow: 0 0 5px #0ff;
        box-shadow: 0 0 10px #0ff;
        transition: all 0.3s ease;

        &:hover {
          background-color: #0ff;
          color: #121212;
          box-shadow: 0 0 20px #0ff, 0 0 40px #0ff;
        }
      }
    }

    li:last-child {
      border-bottom: none;
    }
  }

  &:hover .notifications-dropdown {
    display: block;
  }
}

For some reason, the dropdown keeps disappearing before the mouse can get to it (from the bell). I have tried adding the invisible div, a wrapper, and playing with the positions. Nothing works!!!


Solution

  • So basically you want to allow a grace period of 100-ish milliseconds before setting showDropdown = false. That should cover the distance between the bell and the menu.

    var globalTimeoutId = null;
    
    var bell = document.querySelector(".bell")
    var menu = document.querySelector(".menu")
    
    
    function setElementAsMenuActivator(elem) {
    
      elem.addEventListener('mouseenter', function() {
        clearInterval(globalTimeoutId)
        showMenu()
      })
    
      elem.addEventListener('mouseleave', function() {
        globalTimeoutId = setTimeout(function() {
          hideMenu()
        }, 200)
      })
    }
    
    setElementAsMenuActivator(menu)
    setElementAsMenuActivator(bell)
    
    function showMenu() {
      menu.classList.add("active")
    }
    
    function hideMenu() {
      menu.classList.remove("active")
    }
    div {
      border: 1px solid red;
      padding: 20px;
    
    }
    
    .menu {
      display: none;
    }
    
    .menu.active {
      display: block;
    }
    <div class="bell">hover me</div>
    <hr>
    
    <div class="menu">menu</div>