li as select item using vanilla Javascript

后端 未结 1 371
予麋鹿
予麋鹿 2021-01-17 02:11



        
相关标签:
1条回答
  • 2021-01-17 02:29

    I really like your custom drop-down menu, especially the CSS you included to give some animation to your menu's expand/collapse actions. I've included a modified version of your snippet, and I hope you'll find it to be of help. I'll explain the revisions I made in what is to follow.

    First off, let me begin by noting that I have some minor adjustments to the HTML content. In particular, I included an <input> field before the entire drop-down menu markup with a corresponding <label> element. This has been added to provide a quick manner of illustrating how to capture the value of a particular item from within the selection menu. Now, when you click on one of the drop-down menu's list items, that item's value will be inserted in said <input> field.

    Next, you'll probably notice that I've added a few class or id attributes to some relevant HTML elements. This is largely as a matter of convenience and to make their selection (through the appropriate JavaScript selectors) easier. One important addition, however, are the data-attributes used on the .dropdown-menu unordered list element. It is a somewhat common pattern seen in normal <select> form inputs where one adds a data-attribute containing the value of each individual <option> element (similar to what is shown here). I will address this briefly at a later point briefly. Though seemingly redundant, giving some data-attribute (or, alternatively, fixing the value attribute) to the content within the opening and closing tags provides an easy way of referencing that same value later without having to resort to the innerHTML method. For more on data-attributes, see this MDN article.


    The first thing to note is that I moved all my var declarations to the very top of the script. In your original snippet, you have three separate variable declarations/assignments within the body of a forEach loop:

    dropdownArray.forEach(function(el) {
        var button = el.querySelector('a[data-toggle="dropdown"]'),
            menu = el.querySelector('.dropdown-menu'),
            arrow = button.querySelector('i.icon-arrow');
        /* Some more code follows... */
      });
    

    This is the first thing I'd point out that could be improved upon. Specifically, this is less than desirable because you are essentially reassigning the value of the button, menu and arrow variables in each iteration of the forEach loop. Since these are, instead, meant as constant references to fixed elements of the DOM, it's advisable to pull these out of the loop and, by extension, just group them alongside your two variables at the very top. You'll also notice that I have replaced the var keyword with the ES6 const keyword. While this does not change anything outright, I did so to emphasize that these are fixed (i.e., constant) references to elements of the DOM and may not be reassigned.


    Next, you'll notice I wrote out a function, clickHandler(), that will be used as the callback function to an event listener that will follow. While it's perfectly fine to just write this function inline (i.e., as an anonymous function and argument to the event listener), I have chosen to decouple it into its own space for clarity.

    As you had originally done, I too used the preventDefault() method, here doing so as the first statement in the function's body.

    Where you previously had 14 lines of code to add/remove the appropriate classes on the menu and arrow elements, I have only 4. It is quite simple to understand how you can condense this. First and foremost, there is no explicit need to divvy these class addition/removal actions into if and/or else blocks. Rather, you can simply make use of the toggle() method on the classList property of the Element API. For this to function correctly, however, it is necessary to seed the menu and arrow HTML elements with the CSS classes appropriate for the initial state: .hide and .close, respectively. You'll see this reflected in the HTML markup.

    The last thing to be done with this function is to add an if conditional to check if the target of the user's click is, in fact, one of the drop-down menu's selection boxes (i.e., the <li>s). This can be done in various ways, but perhaps the cleanest is achieved with the contains() method on the Node API. To this end, I began by making a let declaration (again, ES6 syntax) for targ and assigned it to the DOM node upon which the user clicked. (I made a temporary variable to hold this node so that we wouldn't have to repeatedly refer to it as evt.target in the code that follows; this is, furthermore, a good practice and confers performance benefits, if only minor ones, because the JS engine does not have to repeatedly perform lookups but can, instead, refer to a fixed value held in state as a variable). I then specified by if condition, using here the aforementioned contains() method. The provided conditional expression evaluates the binary (true or false) assertion that targ is a descendant node (read: child element) of the .dropdown-menu unordered-list (given by the menu variable). In the event such is true, the value of the <input> element is assigned to be the value of the data-optValue attribute on the clicked <li> element.

     let targ = evt.target;
      if (menu.contains(targ)) {
        selectionInput.value = targ.dataset.optvalue;
      }
    

    In the event the target is not a descendant node of menu, it stands to be reasoned that the user either did not click on an entry of the dropdown-menu or that it was the button element. NOTE: As an alternative to assigning the <input> field's value as the value contained in the custom data- attribute, one could also do as shown below:

    if (evt.target !== selectMenu && evt.target !== button) {
      selectionInput.value = evt.target.innerHTML;
    }
    

    I would (at least from a personal point-of-view), discourage this, as it could complicate things should you, for example, add more HTML content nested within the individual <li> tags of the drop-down menu.


    Finally, we conclude with adding a simple event handler. In this case, I've attached the click event to the selectMenu element (i.e., the top-level <ul> tag) passing it the aforementioned clickHandler() function as a callback.

    const selectMenu = document.querySelector('#custom-select'),
          selectionInput = document.querySelector('#input-field'),
          dropdown = document.querySelectorAll('.dropdown'),
          dropdownArray = Array.prototype.slice.call(dropdown,0),
          button = document.querySelector('a[data-toggle="dropdown"]'),
          menu = document.querySelector('.dropdown-menu'),
          arrow = button.querySelector('i.icon-arrow');
    
    // Event callback function:
    function clickHandler(evt) {  
      evt.preventDefault();
      menu.classList.toggle('show');
      menu.classList.toggle('hide');
      arrow.classList.toggle('open');
      arrow.classList.toggle('close');
      
      let targ = evt.target;
      if (menu.contains(targ)) {
        selectionInput.value = targ.dataset.optvalue;
      }
    }
    
    // 'Click' event registration:
    selectMenu.addEventListener('click', clickHandler, false);
    
    
    
    // Purely your code below:
    Element.prototype.hasClass = function(className) {
      return this.className && new RegExp("(^|\\s)" + className + "(\\s|$)").test(this.className);
    };
    .text-center {
      text-align: center;
    }
    
    *,
    *:before,
    *:after {
      -webkit-border-sizing: border-box;
      -moz-border-sizing: border-box;
      border-sizing: border-box;
    }
    
    .container {
      width: 350px;
      margin: 50px auto;
    }
    .container > ul {
      list-style: none;
      padding: 0;
      margin: 0 0 20px 0;
    }
    
    .title {
      font: normal 40px/1.4 'Pacifico', sans-serif;
      text-align: center;
      color: #2980B9;
    }
    
    .dropdown a { text-decoration: none; }
    .dropdown [data-toggle="dropdown"] {
      position: relative;
      display: block;
      color: white;
      background: #2980B9;
      -webkit-box-shadow: 0 1px 0 #409ad5 inset,
                          0 -1px 0 #20638f inset;
      -moz-box-shadow:    0 1px 0 #409ad5 inset, 
                          0 -1px 0 #20638f inset;
      box-shadow:         0 1px 0 #409ad5 inset,
                          0 -1px 0 #20638f inset;
      text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.3);
      padding: 10px;
    }
    .dropdown [data-toggle="dropdown"]:hover { background: #2c89c6; }
    .dropdown .icon-arrow {
      position: absolute;
      display: block;
      font-size: 0.7em;
      color: #fff;
      top: 14px;
      right: 10px;
    }
    .dropdown .icon-arrow.open {
      -webkit-transform: rotate(-180deg);
      -moz-transform:    rotate(-180deg);
      -ms-transform:     rotate(-180deg);
      transform:         rotate(-180deg);
      -webkit-transition: -webkit-transform 0.6s;
      -moz-transition:    -moz-transform 0.6s;
      -o-transition:      -o-transform 0.6s;
      transition:         transform 0.6s;
    }
    .dropdown .icon-arrow.close {
      -webkit-transform: rotate(0);
      -moz-transform:    rotate(0);
      -ms-transform:     rotate(0);
      transform:         rotate(0);
      -webkit-transition: -webkit-transform 0.6s;
      -moz-transition:    -moz-transform 0.6s;
      -o-transition:      -o-transform 0.6s;
      transition:         transform 0.6s;
    }
    .dropdown .icon-arrow:before { content: '\25BC'; }
    .dropdown .dropdown-menu {
      max-height: 0;
      overflow: hidden;
      list-style: none;
      padding: 0;
      margin: 0;
    }
    .dropdown .dropdown-menu li { padding: 0; }
    .dropdown .dropdown-menu li a {
      display: block;
      color: #6f6f6f;
      background: #EEE;
      -webkit-box-shadow: 0 1px 0 white inset,
                          0 -1px 0 #d5d5d5 inset;
      -moz-box-shadow:    0 1px 0 white inset,
                          0 -1px 0 #d5d5d5 inset;
      box-shadow:         0 1px 0 white inset,
                          0 -1px 0 #d5d5d5 inset;
      text-shadow: 0 -1px 0 rgba(255, 255, 255, 0.3);
      padding: 10px 10px;
    }
    .dropdown .dropdown-menu li a:hover {
      background: #f6f6f6;
    }
    .dropdown .show,
    .dropdown .hide {
      -webkit-transform-origin: 50% 0;
      -moz-transform-origin: 50% 0;
      -ms-transform-origin: 50% 0;
      transform-origin: 50% 0;
    }
    .dropdown .show {
      display: block;
      max-height: 9999px;
      -webkit-transform: scaleY(1);
      -moz-transform:    scaleY(1);
      -ms-transform:     scaleY(1);
      transform:         scaleY(1);
      -webkit-animation: showAnimation 0.5s ease-in-out;
      -moz-animation:    showAnimation 0.5s ease-in-out;
      animation:         showAnimation 0.5s ease-in-out;
      -webkit-transition: max-height 1s ease-in-out;
      -moz-transition:    max-height 1s ease-in-out;
      -o-transition:      max-height 1s ease-in-out;
      transition:         max-height 1s ease-in-out;
    }
    .dropdown .hide {
      max-height: 0;
      -moz-transform: scaleY(0);
      -ms-transform: scaleY(0);
      -webkit-transform: scaleY(0);
      transform: scaleY(0);
      animation: hideAnimation 0.4s ease-out;
      -moz-animation: hideAnimation 0.4s ease-out;
      -webkit-animation: hideAnimation 0.4s ease-out;
      -moz-transition: max-height 0.6s ease-out;
      -o-transition: max-height 0.6s ease-out;
      -webkit-transition: max-height 0.6s ease-out;
      transition: max-height 0.6s ease-out;
    }
    
    @keyframes showAnimation {
      0% {
        -moz-transform: scaleY(0.1);
        -ms-transform: scaleY(0.1);
        -webkit-transform: scaleY(0.1);
        transform: scaleY(0.1);
      }
      40% {
        -moz-transform: scaleY(1.04);
        -ms-transform: scaleY(1.04);
        -webkit-transform: scaleY(1.04);
        transform: scaleY(1.04);
      }
      60% {
        -moz-transform: scaleY(0.98);
        -ms-transform: scaleY(0.98);
        -webkit-transform: scaleY(0.98);
        transform: scaleY(0.98);
      }
      80% {
        -moz-transform: scaleY(1.04);
        -ms-transform: scaleY(1.04);
        -webkit-transform: scaleY(1.04);
        transform: scaleY(1.04);
      }
      100% {
        -moz-transform: scaleY(0.98);
        -ms-transform: scaleY(0.98);
        -webkit-transform: scaleY(0.98);
        transform: scaleY(0.98);
      }
      80% {
        -moz-transform: scaleY(1.02);
        -ms-transform: scaleY(1.02);
        -webkit-transform: scaleY(1.02);
        transform: scaleY(1.02);
      }
      100% {
        -moz-transform: scaleY(1);
        -ms-transform: scaleY(1);
        -webkit-transform: scaleY(1);
        transform: scaleY(1);
      }
    }
    @-moz-keyframes showAnimation {
      0% {
        -moz-transform: scaleY(0.1);
        -ms-transform: scaleY(0.1);
        -webkit-transform: scaleY(0.1);
        transform: scaleY(0.1);
      }
      40% {
        -moz-transform: scaleY(1.04);
        -ms-transform: scaleY(1.04);
        -webkit-transform: scaleY(1.04);
        transform: scaleY(1.04);
      }
      60% {
        -moz-transform: scaleY(0.98);
        -ms-transform: scaleY(0.98);
        -webkit-transform: scaleY(0.98);
        transform: scaleY(0.98);
      }
      80% {
        -moz-transform: scaleY(1.04);
        -ms-transform: scaleY(1.04);
        -webkit-transform: scaleY(1.04);
        transform: scaleY(1.04);
      }
      100% {
        -moz-transform: scaleY(0.98);
        -ms-transform: scaleY(0.98);
        -webkit-transform: scaleY(0.98);
        transform: scaleY(0.98);
      }
      80% {
        -moz-transform: scaleY(1.02);
        -ms-transform: scaleY(1.02);
        -webkit-transform: scaleY(1.02);
        transform: scaleY(1.02);
      }
      100% {
        -moz-transform: scaleY(1);
        -ms-transform: scaleY(1);
        -webkit-transform: scaleY(1);
        transform: scaleY(1);
      }
    }
    @-webkit-keyframes showAnimation {
      0% {
        -moz-transform: scaleY(0.1);
        -ms-transform: scaleY(0.1);
        -webkit-transform: scaleY(0.1);
        transform: scaleY(0.1);
      }
      40% {
        -moz-transform: scaleY(1.04);
        -ms-transform: scaleY(1.04);
        -webkit-transform: scaleY(1.04);
        transform: scaleY(1.04);
      }
      60% {
        -moz-transform: scaleY(0.98);
        -ms-transform: scaleY(0.98);
        -webkit-transform: scaleY(0.98);
        transform: scaleY(0.98);
      }
      80% {
        -moz-transform: scaleY(1.04);
        -ms-transform: scaleY(1.04);
        -webkit-transform: scaleY(1.04);
        transform: scaleY(1.04);
      }
      100% {
        -moz-transform: scaleY(0.98);
        -ms-transform: scaleY(0.98);
        -webkit-transform: scaleY(0.98);
        transform: scaleY(0.98);
      }
      80% {
        -moz-transform: scaleY(1.02);
        -ms-transform: scaleY(1.02);
        -webkit-transform: scaleY(1.02);
        transform: scaleY(1.02);
      }
      100% {
        -moz-transform: scaleY(1);
        -ms-transform: scaleY(1);
        -webkit-transform: scaleY(1);
        transform: scaleY(1);
      }
    }
    @keyframes hideAnimation {
      0% {
        -moz-transform: scaleY(1);
        -ms-transform: scaleY(1);
        -webkit-transform: scaleY(1);
        transform: scaleY(1);
      }
      60% {
        -moz-transform: scaleY(0.98);
        -ms-transform: scaleY(0.98);
        -webkit-transform: scaleY(0.98);
        transform: scaleY(0.98);
      }
      80% {
        -moz-transform: scaleY(1.02);
        -ms-transform: scaleY(1.02);
        -webkit-transform: scaleY(1.02);
        transform: scaleY(1.02);
      }
      100% {
        -moz-transform: scaleY(0);
        -ms-transform: scaleY(0);
        -webkit-transform: scaleY(0);
        transform: scaleY(0);
      }
    }
    @-moz-keyframes hideAnimation {
      0% {
        -moz-transform: scaleY(1);
        -ms-transform: scaleY(1);
        -webkit-transform: scaleY(1);
        transform: scaleY(1);
      }
      60% {
        -moz-transform: scaleY(0.98);
        -ms-transform: scaleY(0.98);
        -webkit-transform: scaleY(0.98);
        transform: scaleY(0.98);
      }
      80% {
        -moz-transform: scaleY(1.02);
        -ms-transform: scaleY(1.02);
        -webkit-transform: scaleY(1.02);
        transform: scaleY(1.02);
      }
      100% {
        -moz-transform: scaleY(0);
        -ms-transform: scaleY(0);
        -webkit-transform: scaleY(0);
        transform: scaleY(0);
      }
    }
    @-webkit-keyframes hideAnimation {
      0% {
        -moz-transform: scaleY(1);
        -ms-transform: scaleY(1);
        -webkit-transform: scaleY(1);
        transform: scaleY(1);
      }
      60% {
        -moz-transform: scaleY(0.98);
        -ms-transform: scaleY(0.98);
        -webkit-transform: scaleY(0.98);
        transform: scaleY(0.98);
      }
      80% {
        -moz-transform: scaleY(1.02);
        -ms-transform: scaleY(1.02);
        -webkit-transform: scaleY(1.02);
        transform: scaleY(1.02);
      }
      100% {
        -moz-transform: scaleY(0);
        -ms-transform: scaleY(0);
        -webkit-transform: scaleY(0);
        transform: scaleY(0);
      }
    }
    <div class="container"> 
      <label for='input-field'>Selection:&nbsp;</label>
      <input type='text' id='input-field' value='' />
      <br /><br />
      
      <ul id='custom-select'>
        <li class="dropdown">
          <a href="#" data-toggle="dropdown">Select Item
            <i class="icon-arrow close"></i>
          </a>
          <ul class="dropdown-menu hide">
            <li><a href="#" data-optValue="Option 1">Option 1</a></li>
            <li><a href="#" data-optValue="Option 2">Option 2</a></li>
            <li><a href="#" data-optValue="Option 3">Option 3</a></li>
            <li><a href="#" data-optValue="Option 4">Option 4</a></li>
          </ul>
        </li>
      </ul>
    </div>

    0 讨论(0)
提交回复
热议问题