Home > database >  jQuery toggleClass breaks on fast click
jQuery toggleClass breaks on fast click

Time:10-18

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 >
    <div >
        <a  href="#">Hi</a>
        <span >
            <span id="toggler-icon"  data-bs-toggle="collapse"
                data-bs-target="#navbarSupportedContent"></span>
        </span>
        <div  id="navbarSupportedContent">
            <ul >
                <li >
                    <a  aria-current="page" href="#">
                        <span ></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?

CodePudding user response:

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 >
    <div >
      <a  href="#">Navbar</a>
      <button  type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
        <span ></span>
      </button>
      <div  id="navbarNavAltMarkup">
        <div >
          <a  aria-current="page" href="#">Home</a>
          <a  href="#">Features</a>
          <a  href="#">Pricing</a>
          <a >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>

<!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 >
  <div >
    <a  href="#">Navbar</a>
    <button  type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
      <span ></span>
    </button>
    <div  id="navbarNavAltMarkup">
      <div >
        <a  aria-current="page" href="#">Home</a>
        <a  href="#">Features</a>
        <a  href="#">Pricing</a>
        <a >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>

CodePudding user response:

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 on 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.

  • Related