zoukankan      html  css  js  c++  java
  • 原生js实现一个自定义下拉单选选择框

      浏览器自带的原生下拉框不太美观,而且各个浏览器表现也不一致,UI一般给的下拉框也是和原生的下拉框差别比较大的,这就需要自己写一个基本功能的下拉菜单/下拉选择框了。最近,把项目中用到的下拉框组件重新封装了一下,以构造函数的方式进行封装,主要方法和事件定义在原型上,下面是主要的实现代码并添加了比较详细的注释,分享出来供大家参考。代码用了ES6部分写法如需兼容低版本浏览器请把相关代码转成es5写法,或者直接bable转下。

      先放个预览图吧,后面有最终的动态效果图:(样式和交互参考了阿里和Iview UI库)

      

    下面是主要的HTML代码(包含部分js调用代码):

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Diy Select</title>
        <link rel="stylesheet" href="index.css">
    </head>
    <body>
        <div id="main" class="main"></div>
    
        <script src="index.js"></script>
        <script>
            document.addEventListener("DOMContentLoaded",function(){
                const select1 = new $Selector({
                    eleSelector:"#main",
                    options:[
                        {name:"选项1",value:"0"},
                        {name:"选项2",value:"1"},
                        {name:"选项3",value:"2"}
                    ],
                    defaultText:"选项2"
                });
            })
        </script>
    </body>
    </html>
    View Code

      HTML部分就放置了一个idmain的包裹div,即为下拉菜单所要添加到的元素。最底部为调用的js,传入相应的参数即可。其中eleSelector为要挂载到的dom节点所在的选择器,此处我们演示,选择挂载到idmaindiv;第2个参数为所要展示的下拉元素数组对象,name为下拉选择的文本内容,value为对应的值,此处我们传入了三个选项对象,生成的下拉框中将会有三个选项;第三个参数为所要展示的默认文本,如果为空,则默认为“未选择”。

     接着就是样式CSS部分:

    * {
        padding: 0;
        margin: 0;
        box-sizing: border-box;
    }
    
    .main {
        padding: 40px;
    }
    
    .my-select {
        display: inline-block;
        width: auto;
        min-width: 80px;
        box-sizing: border-box;
        vertical-align: middle;
        color: #515a6e;
        font-size: 14px;
        line-height: normal;
        position: relative;
    }
    
    .select-selection {
        display: block;
        box-sizing: border-box;
        outline: 0;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        cursor: pointer;
        position: relative;
        background-color: #fff;
        border-radius: 4px;
        border: 1px solid #dcdee2;
        transition: all .2s ease-in-out;
    }
    
    .select-selection:hover,
    .select-selection.select-focus {
        border-color: #57a3f3;
        box-shadow: 0 0 0 2px rgba(45, 140, 240, .2);
    }
    
    .select-selected-value {
        display: block;
        height: 28px;
        line-height: 28px;
        font-size: 12px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        padding-left: 8px;
        padding-right: 24px;
    }
    
    .icon-select-arrow {
        position: absolute;
        top: 50%;
        right: 8px;
        line-height: 1;
        margin-top: -7px;
        font-size: 14px;
        color: #808695;
        transition: all .2s ease-in-out;
        display: inline-block;
        font-style: normal;
        font-weight: 400;
        font-variant: normal;
        text-transform: none;
        text-rendering: auto;
        line-height: 1;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        vertical-align: middle;
    }
    
    .icon-select-arrow::before {
        content: "";
        display: block;
        width: 6px;
        height: 6px;
        background-color: transparent;
        border-left: 1.5px solid #808695;
        border-bottom: 1.5px solid #808695;
        transform: rotate(-45deg);
    }
    
    .select-dropdown {
        width: auto;
        min-width: 80px;
        max-height: 200px;
        overflow: auto;
        margin: 5px 0;
        padding: 5px 0;
        background-color: #fff;
        box-sizing: border-box;
        border-radius: 4px;
        box-shadow: 0 1px 6px rgba(0, 0, 0, .2);
        position: absolute;
        z-index: 2;
        transform-origin: center top 0px;
        transition: all 0.3s;
        will-change: top, left;
        top: 30px;
        left: 0;
        transform: scale(1, 0);
        opacity: 0;
    }
    
    .select-item {
        line-height: normal;
        padding: 7px 16px;
        clear: both;
        color: #515a6e;
        font-size: 12px !important;
        white-space: nowrap;
        list-style: none;
        cursor: pointer;
        transition: background .2s ease-in-out;
    }
    
    .select-item.select-item-selected,
    .select-item:hover {
        color: #2d8cf0;
        background-color: #f3f3f3;
    }
    View Code

    样式部分就不做什么解释了,下面放入压轴的JS,注释写的比较详细,上代码:

      1 /* jshint esversion: 6 */
      2 (function (window, document) {
      3     let Selector = function (option) {
      4         //执行初始化方法,
      5         this._init(option);
      6     };
      7 
      8     Selector.prototype = {
      9         //初始化传入参数并定义初始化的相关变量
     10         _init({
     11             eleSelector = "", //传入的选择器 id,class,tag等,用于将选择框渲染到此选择器所在的元素
     12             options = [{
     13                 name: "请选择",
     14                 value: "0",
     15             }], //传入的下拉框对象,name为选择的文字,value为值
     16             defaultText = "请选择" //提供的默认选择的值
     17         }) {
     18             
     19             //将传入的数据绑定到this上
     20             this.parentEle = document.querySelector(eleSelector) || document.body; //要邦定的dom 
     21             this.options = options; //选择值数组对象
     22             this.defaultText = defaultText; //默认值
     23 
     24             this.dropboxShow = false; //定义存储下拉框的显示隐藏状态
     25             this.defaultValue = ""; //定义村赤默认选中的值
     26             this._creatElement(); //初始化后执行创建元素方法
     27         },
     28 
     29         //创建下拉选择框dom
     30         _creatElement() {
     31             //选择框最外层的包裹元素
     32             let wrapEle = document.createElement("div");
     33             wrapEle.className = "my-select";
     34 
     35             //根据传入的值获取选择框默认的值和内容
     36             this.options.forEach(item => {
     37                 if (item.name === "this.defaultText") {
     38                     this.defaultValue = item.value;
     39                 }
     40             });
     41 
     42             let selectWarpBox = document.createElement("div"); //选择框包裹元素
     43             selectWarpBox.className = "select-selection";
     44 
     45             let inputHideBox = document.createElement("input"); //隐藏保存选择值得元素
     46             inputHideBox.type = "hidden";
     47             inputHideBox.value = this.defaultValue;
     48 
     49             let selectShowBox = document.createElement("div"); //选择框默认展示框
     50             let selectNameBox = document.createElement("span"); //选择框展现的值ele
     51             selectNameBox.className = "select-selected-value";
     52             selectNameBox.id = "select-option";
     53             selectNameBox.innerText = this.defaultText; //将传入的默认值赋值
     54             let selectIcon = document.createElement("i"); //图标ele
     55             selectIcon.className = "arrow-down icon-select-arrow";
     56             //将span和角标添加到外层div
     57             selectShowBox.appendChild(selectNameBox);
     58             selectShowBox.appendChild(selectIcon);
     59 
     60             selectWarpBox.appendChild(inputHideBox);
     61             selectWarpBox.appendChild(selectShowBox);
     62 
     63             //下拉框
     64             let dropbox = document.createElement("div"),
     65                 ulbox = document.createElement("ul");
     66 
     67             dropbox.id = "select-drop";
     68             dropbox.className = "select-dropdown";
     69             ulbox.className = "select-dropdown-list";
     70             //遍历传入的选项数组对象,生成下拉菜单的li元素并赋值
     71             this.options.forEach((item) => {
     72                 let itemLi = document.createElement("li");
     73                 if (this.defaultText === item.name) {
     74                     itemLi.className = "select-item select-item-selected";
     75                 } else {
     76                     itemLi.className = "select-item";
     77                 }
     78 
     79                 itemLi.setAttribute("data-value", item.value);
     80                 itemLi.innerText = item.name;
     81                 ulbox.appendChild(itemLi);
     82 
     83             });
     84             //将下拉框ul推入到包裹元素
     85             dropbox.appendChild(ulbox);
     86 
     87             wrapEle.appendChild(selectWarpBox);
     88             wrapEle.appendChild(dropbox);
     89 
     90             this.parentEle.appendChild(wrapEle); //将生成的下拉框添加到所选元素中
     91 
     92             //把需要操作的dom挂载到当前实例
     93             //this.wrapEle = wrapEle;     //最外层包裹元素
     94             this.eleSelect = selectWarpBox; //选择框
     95             this.eleDrop = dropbox; //下拉框
     96             this.eleSpan = selectNameBox; //显示文字的span节点
     97 
     98             //绑定事件处理函数
     99             this._bind(this.parentEle);
    100         },
    101 
    102         //点击下拉框事件处理函数
    103         _selectHandleClick() {
    104             if (this.dropboxShow) {
    105                 this._selectDropup();
    106             } else {
    107                 this._selectDropdown();
    108             }
    109         },
    110 
    111         //收起下拉选项
    112         _selectDropup() {
    113             this.eleDrop.style.transform = "scale(1,0)";
    114             this.eleDrop.style.opacity = "0";
    115             this.eleSelect.className = "select-selection";
    116             this.dropboxShow = false;
    117         },
    118 
    119         //展示下拉选项
    120         _selectDropdown() {
    121             this.eleDrop.style.transform = "scale(1,1)";
    122             this.eleDrop.style.opacity = "1";
    123             this.eleSelect.className = "select-selection select-focus";
    124             this.dropboxShow = true;
    125         },
    126 
    127         //点击下拉选项进行赋值
    128         _dropItemClick(ele) {
    129             this.defaultValue = ele.getAttribute("data-value");
    130             //document.querySelector("#select-value").value = ele.getAttribute("data-value");
    131             this.eleSpan.innerText = ele.innerText;
    132             ele.className = "select-item select-item-selected";
    133             //对点击选中的其他所有兄弟元素修改class去除选中样式
    134             this._siblingsDo(ele, function (ele) {
    135                 if (ele) {
    136                     ele.className = "select-item";
    137                 }
    138             });
    139             this._selectDropup();
    140         },
    141 
    142         //node遍历是否是子元素包裹元素
    143         _getTargetNode(ele, target) {
    144             //ele是内部元素,target是你想找到的包裹元素
    145             if (!ele || ele === document) return false;
    146             return ele === target ? true : this._getTargetNode(ele.parentNode, target);
    147         },
    148 
    149         //兄弟元素遍历处理函数
    150         _siblingsDo(ele, fn) {
    151 
    152             (function (ele) {
    153                 fn(ele);
    154                 if (ele && ele.previousSibling) {
    155                     arguments.callee(ele.previousSibling);
    156                 }
    157             })(ele.previousSibling);
    158 
    159             (function (ele) {
    160                 fn(ele);
    161                 if (ele && ele.nextSibling) {
    162                     arguments.callee(ele.nextSibling);
    163                 }
    164             })(ele.nextSibling);
    165 
    166         },
    167 
    168         //绑定下拉框事件处理函数
    169         _bind(parentEle) {
    170             let _this = this;
    171             //事件委托到最外层包裹元素进行绑定处理
    172             parentEle.addEventListener("click", function (e) {
    173                 const ele = e.target;
    174                 
    175                 //遍历当前点击的元素,如果是选中框内的元素执行
    176                 if (_this._getTargetNode(ele, _this.eleSelect)) {
    177                     if (_this.dropboxShow) {
    178                         _this._selectDropup();
    179                     } else {
    180                         _this._selectDropdown();
    181                     }
    182                 } else if (ele.className === "select-item") { //如果是点击的下拉框的选项执行
    183                     _this._dropItemClick(ele);
    184                 } else { //点击其他地方隐藏下拉框
    185                     _this._selectDropup();
    186                 }
    187 
    188             });
    189 
    190         }
    191 
    192     };
    193     //将构造函数挂载到全局window
    194     window.$Selector = Selector;
    195 })(window, document);
    View Code

    代码分解:

    (function (window, document) {
        let Selector = function (option) {
            //执行初始化方法,
            this._init(option);
        };
    
    })(window, document);

    这是第一部分:自执行函数,形成封闭的作用域,避免全局污染。同时传入windwo和document对象,window和document作为了作用域中的局部变量, 这样局部作用域就不需要内部函数沿着作用域链再查找到最顶层的window了,提高运行效率。之后定义自定义选择器的构造方法,并执行初始化初始化方法,初始化方法我们将在原型中进行定义,见下文。

     _init({
      eleSelector = "", //传入的选择器 id,class,tag等,用于将选择框渲染到此选择器所在的元素
      options = [{
         name: "请选择",
        value: "0",
      }], //传入的下拉框对象,name为选择的文字,value为值
      defaultText = "请选择" //提供的默认选择的值
      }) {
                //将传入的数据绑定到this上
      this.parentEle = document.querySelector(eleSelector) || document.body; //要邦定的dom 
      this.options = options; //选择值数组对象
      this.defaultText = defaultText; //默认值
    
      this.dropboxShow = false; //定义存储下拉框的显示隐藏状态
      this.defaultValue = ""; //定义村赤默认选中的值
      this._creatElement(); //初始化后执行创建元素方法
    },    

    第二部分为初始化方法,将传入的对象进行解构赋值,并定义变量的默认值,之后将传入的变量挂载到this实例上,同时定义初始化其他变量存储需要的值。数据初始化完毕,此时就改创建生成下拉菜单的元素了,此时变调用创建元素的方法_creatElememt()。接着就是定义创建元素方法了。

        //创建下拉选择框dom
            _creatElement() {
                //选择框最外层的包裹元素
                let wrapEle = document.createElement("div");
                wrapEle.className = "my-select";
    
                //根据传入的值获取选择框默认的值和内容
                this.options.forEach(item => {
                    if (item.name === "this.defaultText") {
                        this.defaultValue = item.value;
                    }
                });
    
                let selectWarpBox = document.createElement("div"); //选择框包裹元素
                selectWarpBox.className = "select-selection";
    
                let inputHideBox = document.createElement("input"); //隐藏保存选择值得元素
                inputHideBox.type = "hidden";
                inputHideBox.value = this.defaultValue;
    
                let selectShowBox = document.createElement("div"); //选择框默认展示框
                let selectNameBox = document.createElement("span"); //选择框展现的值ele
                selectNameBox.className = "select-selected-value";
                selectNameBox.id = "select-option";
                selectNameBox.innerText = this.defaultText; //将传入的默认值赋值
                let selectIcon = document.createElement("i"); //图标ele
                selectIcon.className = "arrow-down icon-select-arrow";
                //将span和角标添加到外层div
                selectShowBox.appendChild(selectNameBox);
                selectShowBox.appendChild(selectIcon);
    
                selectWarpBox.appendChild(inputHideBox);
                selectWarpBox.appendChild(selectShowBox);
    
                //下拉框
                let dropbox = document.createElement("div"),
                    ulbox = document.createElement("ul");
    
                dropbox.id = "select-drop";
                dropbox.className = "select-dropdown";
                ulbox.className = "select-dropdown-list";
                //遍历传入的选项数组对象,生成下拉菜单的li元素并赋值
                this.options.forEach((item) => {
                    let itemLi = document.createElement("li");
                    if (this.defaultText === item.name) {
                        itemLi.className = "select-item select-item-selected";
                    } else {
                        itemLi.className = "select-item";
                    }
    
                    itemLi.setAttribute("data-value", item.value);
                    itemLi.innerText = item.name;
                    ulbox.appendChild(itemLi);
    
                });
                //将下拉框ul推入到包裹元素
                dropbox.appendChild(ulbox);
    
                wrapEle.appendChild(selectWarpBox);
                wrapEle.appendChild(dropbox);
    
                this.parentEle.appendChild(wrapEle); //将生成的下拉框添加到所选元素中
    
                //把需要操作的dom挂载到当前实例
                //this.wrapEle = wrapEle;     //最外层包裹元素
                this.eleSelect = selectWarpBox; //选择框
                this.eleDrop = dropbox; //下拉框
                this.eleSpan = selectNameBox; //显示文字的span节点
    
                //绑定事件处理函数
                this._bind(this.parentEle);
            },

    这一部分主要是创建组成下拉框的dom元素以及对应关系,并将需要的dom节点挂载到this实例对象上,便于后续进行事件处理。最后将组装好的dom节点添加到传入的选择器对象中,即传入的第一个参数所属的dom对象。此时页面中将渲染出一个自定义的下拉选择框。下一步就是绑定事件处理函数,处理交互事件,即最后调用了_bind()方法,将定义的相关事件处理函数绑定到对应的dom。

            //绑定下拉框事件处理函数
            _bind(parentEle) {
                let _this = this;
                //事件委托到最外层包裹元素进行绑定处理
                parentEle.addEventListener("click", function (e) {
                    const ele = e.target;
                    
                    //遍历当前点击的元素,如果是选中框内的元素执行
                    if (_this._getTargetNode(ele, _this.eleSelect)) {
                        if (_this.dropboxShow) {
                            _this._selectDropup();
                        } else {
                            _this._selectDropdown();
                        }
                    } else if (ele.className === "select-item") { //如果是点击的下拉框的选项执行
                        _this._dropItemClick(ele);
                    } else { //点击其他地方隐藏下拉框
                        _this._selectDropup();
                    }
    
                });
    
            }

      //将构造函数挂载到全局window
      window.$Selector = Selector;
     

    这一部分主要就是进行事件的绑定了,我们传入了一个parentELe的dom对象,此对象即为我们传入的选择器对应的元素,然后通过此对象进行事件委托处理下面的其他交互事件。最后将构造函数暴露到全局对象window,以便在全局上的调用。到此,一个自定义下拉菜单就出炉了,下面是动态效果:

    至此,从css自定义的表单元素到下拉框元素都已经自定义完毕,使用bootstrap的同学把这些加进去就能基本保持各浏览器效果一致性和美观性了,就先写道这吧,后续有时间在进行优化。

    海纳百川,有容乃大;壁立千仞,无欲则刚。人要有胸怀方能成大事,不要被欲望所驱使,方能风吹不动浪打不摇。 不积跬步无以至千里,不积小流无以成江海。从事技术工作,要时刻学习积累,即使不能一专多能也应术业有专攻,方能以不变应万变。
  • 相关阅读:
    Splay
    AVL
    Everything 搜索文件
    佛祖保佑 永无Bug
    火绒注入
    STL vector list map 用法
    漏洞挖掘 向目标进程中植入代码
    漏洞挖掘 利用漏洞控制程序执行流程
    漏洞挖掘 堆栈的溢出实践
    追码CM破解笔记
  • 原文地址:https://www.cnblogs.com/websharehome/p/9772755.html
Copyright © 2011-2022 走看看