Headers

Tesla Menu

30 Sep 2022

Tesla Menu - це інтерактивне меню, яке використовується на сайті компанія "Тесла". Анімації реалізована за допомогою JavaScript, який змушує UI елемент слідкувати за курсором.
HTML
SCSS
JS
                            <nav class="navbar">
    <span></span>
    <a class="menu-link" href="#">About Us</a>
    <a class="menu-link" href="#">Candidates</a>
    <a class="menu-link" href="#">Clients</a>
    <a class="menu-link" href="#">Insights</a>
    <a class="menu-link" href="#">Contact Us</a>
</nav>
                        
                            .navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: relative;

  max-width: 800px;
  margin: 100px auto;

  span {
    position: absolute;
    background: rgba(38, 80, 188, 0.6);
    
    -webkit-border-radius: 25px;
    -moz-border-radius: 25px;
    border-radius: 25px;

    height: 90%;

    z-index: -1;
    opacity: 0;

    left: var(--left-position-span);
    width: var(--width-span);
    transition: var(--span-transition);

    &.active {
      opacity: 1;
    }
  }

  .menu-link {
    padding: 10px;
  }
}
                        
                            // Tesla style menu
const navbar = document.getElementsByClassName(`navbar`)[0];

if (navbar) {
  const navbarElements = navbar.querySelectorAll(`a`);
  const spanElement = navbar.querySelector(`span`);
  const activeMenuElement = navbar.querySelector(`a.active`);

  function backgroundMenuPositionFunc(targetElement, flagMouseEnter) {
    const navbarPosition = navbar.getBoundingClientRect();
    const elementPosition = targetElement.getBoundingClientRect();

    let spanPositionLeftStart = elementPosition.left - navbarPosition.left;
    let spanWidthStart = elementPosition.width;

    if (flagMouseEnter) {
      spanElement.style.setProperty(
          "--span-transition",
          `0.5s cubic-bezier(0.75, 0, 0, 1)`
      );
    } else {
      spanElement.style.setProperty(
          "--span-transition",
          `opacity 0.5s ease, visibility 0s 0s`
      );
    }
    spanElement.style.setProperty("--width-span", `${spanWidthStart}px`);
    spanElement.style.setProperty(
        "--left-position-span",
        `${spanPositionLeftStart}px`
    );
  }

  if (activeMenuElement) {
    backgroundMenuPositionFunc(activeMenuElement, true);
    spanElement.classList.add("active");

    navbarElements.forEach((elem) => {
      elem.addEventListener("mouseenter", function (e) {
        backgroundMenuPositionFunc(e.target, true);
      });

      navbar.addEventListener("mouseleave", function (e) {
        backgroundMenuPositionFunc(activeMenuElement, true);
      });
    });
  } else {
    let flagMouseEnter = false;

    navbarElements.forEach((elem) => {
      elem.addEventListener("mouseenter", function (e) {
        backgroundMenuPositionFunc(e.target, flagMouseEnter);
        spanElement.classList.add("active");

        flagMouseEnter = true;
      });
    });

    navbar.addEventListener("mouseleave", function (e) {
      spanElement.classList.remove("active");
      flagMouseEnter = false;
      spanElement.style.setProperty(
          "--span-transition",
          `opacity 0.5s ease, visibility 0s 0.5s`
      );
    });
  }
}
                        

PostCSS Code:

.navbar {
display: flex;
justify-content: space-between;
align-items: center;
position: relative;

max-width: 800px;
margin: 100px auto;

span {
position: absolute;
background: rgba(38, 80, 188, 0.6);

-webkit-border-radius: 25px;
-moz-border-radius: 25px;
border-radius: 25px;

height: 90%;

z-index: -1;
opacity: 0;

left: var(--left-position-span);
width: var(--width-span);
transition: var(--span-transition);

&.active {
opacity: 1;
}
}

.menu-link {
padding: 10px;
}
}
1