0

I have the following code (which I got from here: https://codepen.io/qwertie/pen/QBYMdZ) for a CSS dropdown:

HTML

  <div class="dropdown">
    <span tabindex="0"><span class="active_value">&nbsp;dropdown menu <i class='fas fa-caret-down fa-lg'></i></span></span>
    <div class="dropdownmenu">
        <ul>
      <li class="cb-item"><a href="http://test.net">home page</a></li>
      <li class="cb-item"><a href="http://test2.net">page</a></li>
      <li class="cb-item"><a href="#">Stay on this page</a></li>
      <li class="cb-item"><a href="#">Stay on this page</a></li>
      <li class="cb-item"><a href="#">Stay on this page</a></li>
      <li class="cb-item"><a href="#">Stay on this page</a></li>
      <li class="cb-item"><a href="#">Stay on this page</a></li>
      <li class="cb-item"><a href="#">Stay on this page</a></li>
      <li class="cb-item"><a href="#">fd gddsfgpage</a></li>
      <li class="cb-item"><a href="#">457567456756 757this page</a></li>
      <li class="cb-item"><a href="#">Stay on this page</a></li>
            </ul>
    </div>
  </div>

CSS

.dropdown {
    /* "relative" and "inline-block" (or just "block") are needed
     here so that "absolute" works correctly in children */
    position: relative;
    display: inline-block;
    width: 100%;
    height: 30px;
}

.dropdownmenu {
    background-color: #FFF !important;
    width: max-content;
    width: -moz-max-content;
    width: -webkit-max-content;
    width: -o-max-content;
    max-height: 200px;
    background-color: rgb(255, 255, 255);
    border-bottom-left-radius: 3px;
    border-bottom-right-radius: 3px;
    z-index: 10;
    overflow-y: scroll !important;
    overflow-x: hidden !important;
    border-width: 0px 1px 1px;
    border-style: solid solid solid;
    border-color: rgb(220, 220, 220) rgb(220, 220, 220) rgb(220, 220, 220);
}

span.active_value {
    padding: 6px 0px 6px 0px;
    width: 100%;
    position: absolute;
    background-color: #FFF;
    border: 1px solid #CECECE;
    cursor:pointer;
}

.dropdown i.fa-caret-down {
    position: absolute;
    right: 10px;
}

.dropdownmenu a {
    text-decoration: none;
    color: darkslategray;
}
.cb-item {
    display: block;
    margin: 0px;
    padding: 2px;
}

.cb-item:hover, .cb-item:hover > a {
    color: #fff !important;
    background-color: #006494;
    cursor: pointer;
}



    .dropdown > *:last-child {
        /* Using `display:block` here has two desirable effects:
     (1) Accessibility: it lets input widgets in the dropdown to
         be selected with the tab key when the dropdown is closed. 
     (2) It lets the opacity transition work.
     But it also makes the contents visible, which is undesirable 
     before the list drops down. To compensate, use `opacity: 0`
     and disable mouse pointer events. Another side effect is that
     the user can select and copy the contents of the hidden list,
     but don't worry, the selected content is invisible. */
        display: block;
        opacity: 0;
        pointer-events: none;
        transition: 0.4s; /* fade out */
        position: absolute;
        left: 0;
        top: 100%;
        border: 1px solid #888;
        background-color: #fff;
        box-shadow: 1px 2px 4px 1px #666;
        box-shadow: 1px 2px 4px 1px #4448;
        z-index: 9999;
        min-width: 100%;
        box-sizing: border-box;
    }
    /* List of situations in which to show the dropdown list.
   - Focus dropdown or non-last child of it => show last-child
   - Stay open for focus in last child, unless .dropdownmenu
   - .sticky last child stays open on hover
   - .dropdownmenu stays open on hover, ignores focus in last-child */
.dropdown:focus > *:last-child,
.dropdown > *:focus ~ *:last-child,
.dropdown > .dropdownmenu:last-child:hover {
    display: block;
    opacity: 1;
    transition: 0.15s;
    pointer-events: auto;
}
/* detect Edge/IE and behave if though dropdownmenu is on for all
   dropdowns (otherwise links won't be clickable) */
@supports (-ms-ime-align:auto) {
    .dropdown > *:last-child:hover {
        display: block;
        opacity: 1;
        pointer-events: auto;
    }
}
/* detect IE and do the same thing. */
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
    .dropdown > *:last-child:hover {
        display: block;
        opacity: 1;
        pointer-events: auto;
    }
}

.dropdown:not(.sticky) > *:not(:last-child):focus,
.dropdown:focus {
    pointer-events: none; /* Causes second click to close */
}

What I did now is when I click an item from the dropdownlist I fire this code:

$(document).on('click', '.dropdown .dropdownmenu', function () {
    $(".dropdownmenu").hide("fast");
});
$(document).on('click', '.dropdown .dropdownmenu', function () {
    $(".dropdownmenu").show("fast");
});

But this code can be a bit dodgy since sometimes when it hides the menu, it flickers a bit as the mouse is over an item in the list. I think it's because my jQuery method hides the menu in a different/additional way in hiding the menu which may conflict with the CSS way of hiding/showing the menu.

I thought that the existing CSS code was using opacity to show/hide the menu but that value does not change as far as I can see.

How can I hide the menu on item click in such a way that the basic functionality keeps working?

1 Answer 1

1

Explanation

But this code can be a bit dodgy since sometimes when it hides the menu, it flickers a bit as the mouse is over an item in the list. I think it's because my jQuery method hides the menu in a different/additional way in hiding the menu which may conflict with the CSS way of hiding/showing the menu.

Not exactly. You have a conflict because of what you told jQuery to do. Take a look at your code:

$(document).on('click', '.dropdown .dropdownmenu', function () {
    $(".dropdownmenu").hide("fast");
});
$(document).on('click', '.dropdown .dropdownmenu', function () {
    $(".dropdownmenu").show("fast");
});

You have two things that should happen on a click:

  • $(".dropdownmenu").show("fast");
  • $(".dropdownmenu").hide("fast");

We could summarize it to this to make the problem more obvious:

$(document).on('click', '.dropdown .dropdownmenu', function () {
    $(".dropdownmenu").hide("fast");
    $(".dropdownmenu").show("fast");
});

What your code does is to execute those two functions each time no matter what the state of the dropdown is so it interferes.


Solution

Since your dropdown does show the menu without JS you don't need it so let's remove it.

$(document).on('click', '.dropdown .dropdownmenu', function() {
  $(".dropdownmenu").hide("fast");
});

Problem is: jQuery will use a different hiding method (which is not the reason for the flickering!) by applying display: none to the style attribute after the animation so this only works one time. What your CSS does to hide/show the menu is to make it invisible by using opacity: 0 which makes it more complicated. To solve this problem I removed two small blocks of CSS and changed the jQuery to this:

$('.active_value').on('click', () => {
  $(".dropdownmenu").toggleClass('opened');
});

$(document).mouseup(function(e) {
  if (!$('.active_value').is(e.target)) {
    $(".dropdownmenu").removeClass('opened');
  }
});
.dropdown {
  /* "relative" and "inline-block" (or just "block") are needed
     here so that "absolute" works correctly in children */
  position: relative;
  display: inline-block;
  width: 100%;
  height: 30px;
}

.dropdownmenu {
  background-color: #FFF !important;
  width: max-content;
  width: -moz-max-content;
  width: -webkit-max-content;
  width: -o-max-content;
  max-height: 200px;
  background-color: rgb(255, 255, 255);
  border-bottom-left-radius: 3px;
  border-bottom-right-radius: 3px;
  z-index: 10;
  overflow-y: scroll !important;
  overflow-x: hidden !important;
  border-width: 0px 1px 1px;
  border-style: solid solid solid;
  border-color: rgb(220, 220, 220) rgb(220, 220, 220) rgb(220, 220, 220);
}

span.active_value {
  padding: 6px 0px 6px 0px;
  width: 100%;
  position: absolute;
  background-color: #FFF;
  border: 1px solid #CECECE;
  cursor: pointer;
}

.dropdown i.fa-caret-down {
  position: absolute;
  right: 10px;
}

.dropdownmenu a {
  text-decoration: none;
  color: darkslategray;
}

.cb-item {
  display: block;
  margin: 0px;
  padding: 2px;
}

.cb-item:hover,
.cb-item:hover>a {
  color: #fff !important;
  background-color: #006494;
  cursor: pointer;
}

.dropdown>*:last-child {
  /* Using `display:block` here has two desirable effects:
     (1) Accessibility: it lets input widgets in the dropdown to
         be selected with the tab key when the dropdown is closed. 
     (2) It lets the opacity transition work.
     But it also makes the contents visible, which is undesirable 
     before the list drops down. To compensate, use `opacity: 0`
     and disable mouse pointer events. Another side effect is that
     the user can select and copy the contents of the hidden list,
     but don't worry, the selected content is invisible. */
  display: block;
  opacity: 0;
  pointer-events: none;
  transition: 0.4s;
  /* fade out */
  position: absolute;
  left: 0;
  top: 100%;
  border: 1px solid #888;
  background-color: #fff;
  box-shadow: 1px 2px 4px 1px #666;
  box-shadow: 1px 2px 4px 1px #4448;
  z-index: 9999;
  min-width: 100%;
  box-sizing: border-box;
}


/* List of situations in which to show the dropdown list.
   - Focus dropdown or non-last child of it => show last-child
   - Stay open for focus in last child, unless .dropdownmenu
   - .sticky last child stays open on hover
   - .dropdownmenu stays open on hover, ignores focus in last-child */


/* detect Edge/IE and behave if though dropdownmenu is on for all
   dropdowns (otherwise links won't be clickable) */

@supports (-ms-ime-align:auto) {
  .dropdown>*:last-child:hover {
    display: block;
    opacity: 1;
    pointer-events: auto;
  }
}


/* detect IE and do the same thing. */

@media all and (-ms-high-contrast: none),
(-ms-high-contrast: active) {
  .dropdown>*:last-child:hover {
    display: block;
    opacity: 1;
    pointer-events: auto;
  }
}

.dropdownmenu.opened {
  display: block;
  opacity: 1;
  transition: 0.15s;
  pointer-events: auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="dropdown">
  <span tabindex="0"><span class="active_value">&nbsp;dropdown menu <i class='fas fa-caret-down fa-lg'></i></span></span>
  <div class="dropdownmenu">
    <ul>
      <li class="cb-item"><a href="http://test.net">home page</a></li>
      <li class="cb-item"><a href="http://test2.net">page</a></li>
      <li class="cb-item"><a href="#">Stay on this page</a></li>
      <li class="cb-item"><a href="#">Stay on this page</a></li>
      <li class="cb-item"><a href="#">Stay on this page</a></li>
      <li class="cb-item"><a href="#">Stay on this page</a></li>
      <li class="cb-item"><a href="#">Stay on this page</a></li>
      <li class="cb-item"><a href="#">Stay on this page</a></li>
      <li class="cb-item"><a href="#">fd gddsfgpage</a></li>
      <li class="cb-item"><a href="#">457567456756 757this page</a></li>
      <li class="cb-item"><a href="#">Stay on this page</a></li>
    </ul>
  </div>
</div>

I used a different approach where I removed the CSS :hover effects completely to use a JS only approach to open/close the dropdown. If you compare your CSS and my CSS you will find the changes very quickly. I removed one class and added the opened class.

Also, keep in mind that this is one of many approaches but the one I chose.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.