写在前面
代码系本人原创,如果存在某些地方可以改进或者大家有更好的思路,麻烦大家在评论区进行指正,蟹蟹大家。
实现效果
博文效果:程序重在模拟Select组件的业务逻辑,部分颜色和padding可能会和原生的效果存在出入。有强迫症的小伙伴可以用拾色器和截图工具深究颜色和padding值,然后自行修改。
-
初始效果:
-
点击效果:
-
鼠标悬浮效果 & 点击效果:
-
鼠标选择效果:
-
鼠标点击输入框外的效果:
需求分析
* 初始效果:
- 原生Select组件由文本框和字体图标组成,使用placaholder作为默认输入提示,使用readonly禁止文本编辑。其中的字体颜色均为灰色,没有input自带的外廓样式,需要修改css样式:
input {
outline: none; //消除input自带的外廓样式
box-sizing: border-box; //盒模型,消除padding给input输入框带来的宽高影响
color: #606266 !important; //使用!important给予更高的优先级
}
input:focus {
border: 1px solid #409eff; //点击输入框,改变输入框的边框样式,原生组件边框的出现比较缓和,是因为有加过渡动画
}
- 原生Select组件的字体图标使用了阿里字体,需要引入阿里字体,小伙伴可以自行下载和导入需要使用的字体图标。字体图标和文本框需要组合在一起,给icon添加一个icon类,赋予它绝对定位,并给input输入框一个相对定位,调整合适的位置即可。
<head>
<!--引入项目所在的阿里字体CSS文件-->
<link rel="stylesheet" href="https://at.alicdn.com/t/font_2405460_rrlnzuc0m8.css">
</head>
<body>
<!--类名使用iconfont + 项目上定义的类名调用阿里字体,默认使用下箭头图标加载-->
<i id="icon" class="iconfont icon-shanglajiantou icon"></i>
</body>
.input {
width: 240px;
height: 40px;
padding: 0px 30px 0px 15px;
border: 1px solid #dcdfe6;
position: relative;
}
.icon {
position: absolute;
left: 218px;
top: 12px;
}
* 点击效果:
- 原生Select组件点击文本框后会切换字体图标,同时显示选择列表。说明文本框和字体图标放在同一层,同时需要使用一个div进行包裹,点击会触发一个事件。所以,对应的html代码应该是:
<div onclick="handleWarpClicked()" id="select">
<input id="input" type="text" class="input" placeholder="请选择" readonly="readonly">
<!--类名使用iconfont + 项目上定义的类名调用阿里字体-->
<i id="icon" class="iconfont icon-shanglajiantou icon"></i>
</div>
- 切换图标使用一个全局变量进行判断,每次点击取反即可。如果为真,就取上箭头的字体图标,同时显示选择列表;如果为假,就取下箭头的字体图标,同时隐藏选择列表。所以div对应的js点击代码应该是:
var up = false
//设置下拉图片的样式
function setDownIcon() {
_getElementById("icon").className = "iconfont icon-shanglajiantou icon"
_getElementById("warp").style.display = "none"
}
//包裹容器点击事件
function handleWarpClicked() {
up = !up
if(up) {
_getElementById("icon").className = "iconfont icon-shangjiantou icon"
_getElementById("warp").style.display = "block"
_getElementById("input").style.border = "1px solid #409eff"
} else setDownIcon()
}
* 鼠标悬浮效果 & 点击效果 & 鼠标选择效果:
- 构建选择列表的html代码和对应样式
<body>
<ul class="warp" id="warp">
<!--使用CSS控制的正三角-->
<span class="triangle"></span>
<li onmouseover="Onmouseover(event)" onclick="handleClicked(event)">黄金糕</li>
<li onmouseover="Onmouseover(event)" onclick="handleClicked(event)">双皮奶</li>
<li onmouseover="Onmouseover(event)" onclick="handleClicked(event)">蚵仔煎</li>
<li onmouseover="Onmouseover(event)" onclick="handleClicked(event)">龙须面</li>
<li onmouseover="Onmouseover(event)" onclick="handleClicked(event)">北京烤鸭</li>
</ul>
</body>
ul {
list-style: none;
}
.triangle {
position: absolute;
left: 50px;
top: 50px;
height:0px;
width:0px;
border-bottom:10px solid #f5f7fa;
border-left:10px solid transparent;
border-right:10px solid transparent;
}
.warp {
height: 180px;
width: 238px;
background-color: #f5f7fa;
padding: 5px 0px;
display: none;
margin-top: 20px;
}
.warp li {
width: 238px;
height: 34px;
padding: 0px 20px;
box-sizing: border-box;
line-height: 34px;
}
- 鼠标悬浮到选择列表上,当前列表项的背景颜色会发生更改。鼠标从选择列表移出后,仍会保留相应的背景颜色。如果使用CSS的hover伪类选择器,无法达到移出选择区域仍保留背景颜色状态的效果,所以可以考虑给每个小li增添一个
onmouseover
事件,用来保留更新的背景颜色状态。点击当前小li,当前小li的颜色和字体会发生变化,同时文本框的边框会发生变化。所以考虑为每个小li增添一个onclick
事件,用来接收小li的值,并将得到的值赋给input文本框的value。每次点击后对up进行取反,同时关闭选择列表。
//小li点击事件
function handleClicked(event){
const target = event.target
//需要转换为数组
const tags =Array.from(document.getElementsByTagName("li"))
//拿到所有小li,遍历循环,使用排他思想重置样式
tags.forEach(element => {
element.style.color = "#000000"
element.style.fontWeight = "normal"
});
//为选中的小li添加样式
_getElementById("input").value = target.innerHTML
_getElementById("input").style.color = "#000"
_getElementById("input").style.border = "1px solid #409eff"
_getElementById("warp").style.display = "none"
_getElementById("icon").className = "iconfont icon-shanglajiantou icon"
target.style.color = "#409eff"
target.style.fontWeight = "bold"
//点击小li需要对up进行取反
up = !up
}
//添加鼠标事件
function Onmouseover(e) {
//排他思想,清除所有小li的背景样式
const tags =Array.from(document.getElementsByTagName("li"))
tags.forEach(element => {
element.style.backgroundColor = "#f5f7fa"
});
//为选中的小li添加背景样式
e.target.style.backgroundColor = "pink"
}
* 鼠标点击输入框外的效果:
- 点击输入框外的区域,会关闭选择列表,同时重置输入框的边框样式。既然是点击输入框外产生的效果,就必须得先把输入框这个区域给找出来。所以,本程序定义了一个全局变量root,用于判断当前点击的区域。同时由于页面根元素dom的点击事件可能会和文本框以及选择列表产生冲突,需要进行判断。
var root = document.documentElement
//为页面添加鼠标事件
root.onmouseup = function(e) {
//如果鼠标松开区域在ul容器外,隐藏ul容器,并设置输入框的边框样式
//此处会和输入框点击事件冲突,点击输入框会同时执行页面根元素的点击事件和输入框点击事件,都会隐藏选择列表,
//但是由于input:focus的css权重要大于_getElementById("input").style.border = "1px solid #dcdfe6"的css权重,
//所以点击输入框,最终还是会显示为蓝色边框。
if(e.target.id !== "warp") {
_getElementById("warp").style.display = "none"
_getElementById("input").style.border = "1px solid #dcdfe6"
}
//如果鼠标松开区域不在输入框和ul内部,就给up取反,此种方法有点取巧
//通过选择列表和文本框的e.path.length来判断,刚好它们的值都为6
//点击区域在选择列表和文本框外需要对up进行取反,选择列表和文本框点击事件都已经有对up取反。
if(e.path.length !== 6) {
up = !up
}
setDownIcon()
}
源码放送
本程序由html文件、css文件夹和js文件夹构成,它们处于同一层级。其中,css文件夹又包括select.css文件,js文件夹由select.js(用于处理选择器逻辑)和help.js(做项目养成的习惯,用于定义公共方法)组成。
select.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Element Select</title>
<link rel="stylesheet" href="css/select.css" type="text/css">
<!--引入项目所在的阿里字体CSS文件-->
<link rel="stylesheet" href="https://at.alicdn.com/t/font_2405460_rrlnzuc0m8.css">
</head>
<body>
<div onclick="handleWarpClicked()" id="select">
<input id="input" type="text" class="input" placeholder="请选择" readonly="readonly">
<!--类名使用iconfont + 项目上定义的类名调用阿里字体-->
<i id="icon" class="iconfont icon-shanglajiantou icon"></i>
</div>
<ul class="warp" id="warp">
<!--使用CSS控制的正三角-->
<span class="triangle"></span>
<li onmouseover="Onmouseover(event)" onclick="handleClicked(event)">黄金糕</li>
<li onmouseover="Onmouseover(event)" onclick="handleClicked(event)">双皮奶</li>
<li onmouseover="Onmouseover(event)" onclick="handleClicked(event)">蚵仔煎</li>
<li onmouseover="Onmouseover(event)" onclick="handleClicked(event)">龙须面</li>
<li onmouseover="Onmouseover(event)" onclick="handleClicked(event)">北京烤鸭</li>
</ul>
<!--引入公共方法,且由于JS是同步执行,help.js必须在select.js之上,不然无法引用公共方法-->
<script src="js/help.js"></script>
<script src="js/select.js"></script>
</body>
</html>
select.css:
* {
margin: 0; //项目初始化,清除padding和margin,可加可不加,看个人习惯
padding: 0;
}
ul {
list-style: none;
}
.input {
width: 240px;
height: 40px;
padding: 0px 30px 0px 15px;
border: 1px solid #dcdfe6;
position: relative;
}
.triangle {
position: absolute;
left: 50px;
top: 50px;
height:0px;
width:0px;
border-bottom:10px solid #f5f7fa;
border-left:10px solid transparent;
border-right:10px solid transparent;
}
input {
outline: none;
box-sizing: border-box;
color: #606266 !important;
}
input:focus {
border: 1px solid #409eff;
}
.icon {
position: absolute;
left: 218px;
top: 12px;
}
.warp {
height: 180px;
width: 238px;
background-color: #f5f7fa;
padding: 5px 0px;
display: none;
margin-top: 20px;
}
.warp li {
width: 238px;
height: 34px;
padding: 0px 20px;
box-sizing: border-box;
line-height: 34px;
}
help.js:
//获取id为id的dom元素
function _getElementById(id) {
return document.getElementById(id)
}
select.js:
var up = false
var root = document.documentElement
//设置下拉图片的样式
function setDownIcon() {
_getElementById("icon").className = "iconfont icon-shanglajiantou icon"
_getElementById("warp").style.display = "none"
}
//包裹容器点击事件
function handleWarpClicked() {
up = !up
if(up) {
_getElementById("icon").className = "iconfont icon-shangjiantou icon"
_getElementById("warp").style.display = "block"
_getElementById("input").style.border = "1px solid #409eff"
} else setDownIcon()
}
//小li点击事件
function handleClicked(event){
const target = event.target
//需要转换为数组
const tags =Array.from(document.getElementsByTagName("li"))
//拿到所有小li,遍历循环,使用排他思想重置样式
tags.forEach(element => {
element.style.color = "#000000"
element.style.fontWeight = "normal"
});
//为选中的小li添加样式
_getElementById("input").value = target.innerHTML
_getElementById("input").style.color = "#000"
_getElementById("input").style.border = "1px solid #409eff"
_getElementById("warp").style.display = "none"
_getElementById("icon").className = "iconfont icon-shanglajiantou icon"
target.style.color = "#409eff"
target.style.fontWeight = "bold"
//点击小li需要对up进行取反
up = !up
}
//添加鼠标事件
function Onmouseover(e) {
//排他思想,清除所有小li的背景样式
const tags =Array.from(document.getElementsByTagName("li"))
tags.forEach(element => {
element.style.backgroundColor = "#f5f7fa"
});
//为选中的小li添加背景样式
e.target.style.backgroundColor = "pink"
}
//为页面添加鼠标事件
root.onmouseup = function(e) {
//如果鼠标松开区域在ul容器外,隐藏ul容器,并设置输入框的边框样式
//此处会和输入框点击事件冲突,点击输入框会同时执行页面根元素的点击事件和输入框点击事件,都会隐藏选择列表,
//但是由于input:focus的css权重要大于_getElementById("input").style.border = "1px solid #dcdfe6"的css权重,
//所以点击输入框,最终还是会显示为蓝色边框。
if(e.target.id !== "warp") {
_getElementById("warp").style.display = "none"
_getElementById("input").style.border = "1px solid #dcdfe6"
}
//如果鼠标松开区域不在输入框和ul内部,就给up取反,此种方法有点取巧
//通过选择列表和文本框的e.path.length来判断,刚好它们的值都为6
//点击区域在选择列表和文本框外需要对up进行取反,选择列表和文本框点击事件都已经有对up取反。
if(e.path.length !== 6) {
up = !up
}
setDownIcon()
}
写在最后
js功底很重要,大厂都比较注重
js功底很重要,大厂都比较注重
js功底很重要,大厂都比较注重
重要的话说三遍,做完小程序项目,以后有时间需要多多学习和练习js。麻烦学会的兄dei比个小心心哦。