Innovin
Innovin

Reputation: 830

jQuery toggleClass breaks on fast click

I have a dropdown menu made in Bootstrap. I implemented a jQuery toggleClass on the dropdown menu icon that changes the icon on click. Normally, it is supposed to show the hamburger icon when the menu is closed, then change to an X icon when the menu is open. But the problem here is that, if the menu is clicked too fast(a double click), the reverse would then be the case(i.e The menu then shows an X icon when closed and a hamburger icon when open). And I dont want that. Here is the code snippet.

$("span.toggler").click(function() {

                    $("#toggler-icon").toggleClass("fa-bars fa-times");
            })
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
 
   
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="#">Hi</a>
        <span class="toggler">
            <span id="toggler-icon" class="fa-solid navbar-toggler fa-bars fa-2x text-white" data-bs-toggle="collapse"
                data-bs-target="#navbarSupportedContent"></span>
        </span>
        <div class="collapse text-white navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                <li class="nav-item">
                    <a class="nav-link active" aria-current="page" href="#">
                        <span class="fa-solid fa-home"></span> Dashboard </a>
                </li>

               
                    </ul>
                
        </div>
    </div>
</nav>

<script src="https://code.jquery.com/jquery-3.6.1.min.js"
        integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script>
         <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>

Is there a way I can prevent this?

Upvotes: 1

Views: 231

Answers (2)

Tobiah Rex
Tobiah Rex

Reputation: 2358

NOTE: My Answer is more about giving you intuition on WHAT the problem is and WHY it's happening. I think @kmoser's suggestion is the best suggestion.

The issue is JavaScript Call Stack is competing with the Render Event Loop creating a race condition which the render events will always loose. But Why?

Render event's travel thru the browsers Event-Loop onto JavaScript's Call Stack. BUT, button clicks travel directly to the Call Stack, skipping the event loop. Which means, button clicks are executed immediately (in most cases: synchronously) while render events are executed asynchronously (most of the time) and thus the render events don't have a chance to be appended to the Call Stack in the order you intended.

Why? Because they typically carryout animations! and animations are always syntactically described with some millisecond delay runtime.

CSS Stylesheet Syntax Example: animation: .5s linear 1s infinite alternate slidein;

The .5s is an asynchronous side-effect. Therefore it will automatically be put onto the Browsers Event-Loop, and whenever JavaScript's Call-Stack says

Hey event loop, i'm finished with all these button clicks, I'm ready for that animation you're holding!

then it executes the animation by pushing it onto the Call Stack.

In your case, if you looked under-the-hood into Bootstraps CSS files you'd find some animating syntax per my example for the dropdown with the millisecond runtime defined.

The solution?

  • As @kmoser pointed out, the intended way to accomplish your desired task is to trigger css icon changes (Hamburger to X) based on css actions (animations: dropdown open, dropdown closed), rather than user actions.

Hope that helps.

Upvotes: 1

kmoser
kmoser

Reputation: 9273

If you use the right selectors, you can style the toggler icon based on the navbar's state (collapsed or not).

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Bootstrap demo</title>
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer" />

  <style>
    button.navbar-toggler[aria-expanded="true"] .navbar-toggler-icon {
      background-image: none;
    }

    button.navbar-toggler[aria-expanded="true"] .navbar-toggler-icon::before {
      content: "\58";
      line-height: 2rem;
    }
  </style>
</head>

<body>
  <nav class="navbar navbar-expand-lg bg-light">
    <div class="container-fluid">
      <a class="navbar-brand" href="#">Navbar</a>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
        <span class="fa navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
        <div class="navbar-nav">
          <a class="nav-link active" aria-current="page" href="#">Home</a>
          <a class="nav-link" href="#">Features</a>
          <a class="nav-link" href="#">Pricing</a>
          <a class="nav-link disabled">Disabled</a>
        </div>
      </div>
    </div>
  </nav>
  <h1>Hello, world!</h1>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
</body>

</html>

Upvotes: 2

Related Questions