原生js實現一個自定義下拉單選選擇框

  瀏覽器自帶的原生下拉框不太美觀,並且各個瀏覽器表現也不一致,UI通常給的下拉框也是和原生的下拉框差異比較大的,這就須要本身寫一個基本功能的下拉菜單/下拉選擇框了。最近,把項目中用到的下拉框組件從新封裝了一下,以構造函數的方式進行封裝,主要方法和事件定義在原型上,下面是主要的實現代碼並添加了比較詳細的註釋,分享出來供你們參考。代碼用了ES6部分寫法如需兼容低版本瀏覽器請把相關代碼轉成es5寫法,或者直接bable轉下。css

  先放個預覽圖吧,後面有最終的動態效果圖:(樣式和交互參考了阿里和Iview UI庫)html

  

 

下面是主要的HTML代碼(包含部分js調用代碼):node

<!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爲對應的值,此處咱們傳入了三個選項對象,生成的下拉框中將會有三個選項;第三個參數爲所要展現的默認文本,若是爲空,則默認爲「未選擇」。web

 接着就是樣式CSS部分:bootstrap

* {
    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了,提升運行效率。以後定義自定義選擇器的構造方法,並執行初始化初始化方法,初始化方法咱們將在原型中進行定義,見下文。app

 _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

    //建立下拉選擇框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。ide

        //綁定下拉框事件處理函數
        _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的同窗把這些加進去就能基本保持各瀏覽器效果一致性和美觀性了,就先寫道這吧,後續有時間在進行優化。

相關文章
相關標籤/搜索