浏览器自带的原生下拉框不太美观,而且各个浏览器表现也不一致,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>

  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;
}

样式部分就不做什么解释了,下面放入压轴的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了,提高运行效率。之后定义自定义选择器的构造方法,并执行初始化初始化方法,初始化方法我们将在原型中进行定义,见下文。

 _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的同学把这些加进去就能基本保持各浏览器效果一致性和美观性了,就先写道这吧,后续有时间在进行优化。

原生js实现一个自定义下拉单选选择框的更多相关文章

  1. excel选择元角分下拉菜单选择框自动变更数字

    excel选择元角分下拉菜单选择框自动变更数字 (M2列),数据-->数据有效性-->在“允许”栏中选择序列-->在“来源”栏中输入:分,角,元单位倍数公式(M4列):=IF(M2= ...

  2. 原生js实现简单的下拉刷新功能

    前言: 我们在浏览移动端web页面的时候,经常会用到下拉刷新. 现在我们用原生的js实现这个非常简单的下拉刷新功能. (温馨提示:本文比较基础,功能也很简单.写的不好的地方,希望大神提点一二.) 一. ...

  3. 原生js手机端触摸下拉刷新

    废话不多说,直接上代码,这里感谢我的好朋友,豆姐 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ...

  4. 原生js实现简单的下拉加载

    #获取当前滚动条的高度document.documentElement.scrollTop #获取当前窗口的高度 document.documentElement.clientHeight #获取当前 ...

  5. easyui源码翻译1.32--Combo(自定义下拉框)

    前言 扩展自$.fn.validatebox.defaults.使用$.fn.combo.defaults重写默认值对象.下载该插件翻译源码 自定义下拉框显示一个可编辑的文本框和下拉面板在html页面 ...

  6. jquery美化select,自定义下拉框样式

    select默认的样式比较丑,有些应用需要美化select,在网上找到一个很好的美化样式效果,本人很喜欢,在这里分享一下. <!DOCTYPE html PUBLIC "-//W3C/ ...

  7. Combo( 自定义下拉框) 组件

    本节课重点了解 EasyUI 中 Combo(自定义下拉框)组件的使用方法,这个组件依赖于ValidateBox(验证框)组件 一. 加载方式自定义下拉框不能通过标签的方式进行创建.<input ...

  8. 关于safari上的select宽高问题小技,自定义下拉框

    之前一直用windows做开发,最近换了个mac,在几经折腾之下,安装完了各种开发工具,IDE等,然后欣然打开自己正在开发的网站.突然发现mac上所有的下拉框都变了,都是默认样式,无论padding, ...

  9. 使用 CSS overscroll-behavior 控制滚动行为:自定义下拉刷新和溢出效果

    CSS 的新属性 overscroll-behavior 允许开发者覆盖默认的浏览器滚动行为,一般用在滚动到顶部或者底部. 背景 滚动边界和滚动链接(boundary & chaining) ...

随机推荐

  1. 基于 ramfs 的 OTA

    背景 默认的 OTA 方案是基于 recovery 系统完成的.某个产品考虑产品形态和 flash 容量之后,计划去掉 recovery 系统(不考虑掉电安全),这就需要 OTA 方案能支持在只有单个 ...

  2. C#类库推荐 拼多多.Net SDK,开源免费!

    背景介绍 近两年拼多多的发展非常迅速,即便口碑一般,也没有网页端,奈何我们已经全面小康,6亿月收入1000以下,9亿月收入2000以下,所以因为价格原因使用拼多多的用户也越来越多了.同样的,拼多多也开 ...

  3. JavaScript中关于获取浏览器可视窗口的几个兼容性写法的理解

    1.浏览器可视窗口的概述: 浏览器可视区域不是网页的body的大小.可视区指的是浏览器减去上面菜单栏.工具栏,下面状态栏和任务栏,右边滚动条(如果有的话)后的中间网页内容的单页面积大小.而body大小 ...

  4. 基于JavaScript的表格设计:按序添加或删除班级的学生信息

    目的: 制作一个表格,显示班级的学生信息 功能: 鼠标移到不同行,背景色发生改变,离开恢复原背景色 添加.删除按钮,可添加,可删除. 程序流程: 首先先建立绑定事件函数. 其次建立鼠标移动改变背景色函 ...

  5. 网络爬虫:利用selenium,pyquery库抓取并处理京东上的图片并存储到使用mongdb数据库进行存储

    一,环境的搭建已经简单的工具介绍 1.selenium,一个用于Web应用程序测试的工具.其特点是直接运行在浏览器中,就像真正的用户在操作一样.新版本selenium2集成了 Selenium 1.0 ...

  6. 安装Ubuntu虚拟机

    centos已经满足不了我了,这里就装了个虚拟机,等有钱了再单配台单系统的Linux主机. 一.下载Ubuntu的ISO文件 用国内的网易镜像站点 进去点个16.04.6,然后下个64位的.iso就好 ...

  7. 小白也能看懂的Redis教学基础篇——redis神秘的数据结构

    各位看官大大们,周末好! 作为一个Java后端开发,要想获得比较可观的工资,Redis基本上是必会的(不要问我为什么知道,问就是被问过无数次).那么Redis是什么,它到底拥有什么神秘的力量,能获得众 ...

  8. XSS基础笔记 from 《Web安全攻防 渗透测试实战指南》

    XSS漏洞介绍 跨站脚本(Cross Site Scripting, 简称为XSS或跨站脚本或跨站脚本攻击)是一种针对网站应用程序的安全漏洞攻击技术,是代码注入的一种.它允许恶意用户将代码注入网页,其 ...

  9. Django_项目开始

    如何初始Django运行环境? 1. 安装python 2. 创建Django项目专用的虚拟环境 http://www.cnblogs.com/2bjiujiu/p/7365876.html 3.进入 ...

  10. 神奇的字符串匹配:扩展KMP算法

    引言 一个算是冷门的算法(在竞赛上),不过其算法思想值得深究. 前置知识 kmp的算法思想,具体可以参考 → Click here trie树(字典树). 正文 问题定义:给定两个字符串 S 和 T( ...