瀏覽器自帶的原生下拉框不太美觀,並且各個瀏覽器表現也不一致,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>
HTML部分就放置了一個id爲main的包裹div,即爲下拉菜單所要添加到的元素。最底部爲調用的js,傳入相應的參數便可。其中eleSelector爲要掛載到的dom節點所在的選擇器,此處咱們演示,選擇掛載到id爲main的div;第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; }
樣式部分就不作什麼解釋了,下面放入壓軸的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);
代碼分解:瀏覽器
(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(); } }); }
這一部分主要就是進行事件的綁定了,咱們傳入了一個parentELe的dom對象,此對象即爲咱們傳入的選擇器對應的元素,而後經過此對象進行事件委託處理下面的其餘交互事件。最後將構造函數暴露到全局對象window,以便在全局上的調用。到此,一個自定義下拉菜單就出爐了,下面是動態效果:
至此,從css自定義的表單元素到下拉框元素都已經自定義完畢,使用bootstrap的同窗把這些加進去就能基本保持各瀏覽器效果一致性和美觀性了,就先寫道這吧,後續有時間在進行優化。