老师,能帮忙解决下问题吗
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" type="text/css" href="./css/style.css">
</head>
<body>
<div id="wrap">
<div class="custom-select">
<div class="title">自定义select</div>
<div class="selector">
<div class="selector-content">
<input class="selector-text" type="text" readonly value="未选择">
<div class="selector-icon">▼</div>
</div>
<div class="selector-item">
<input type="text" class="search-btn" placeholder="搜索">
<div class="search-list">
</div>
</div>
</div>
<div class="selector-tip">
<div class="old-value">之前的值 未选择 - undefined</div>
<div class="new-value">改变后的值是 Vue - 4</div>
</div>
</div>
<div class="native-select">
<div class="title">原生select</div>
<select class="options">
<option>Babel</option>
<option>Webpack</option>
<option>Rollup</option>
<option>Vue</option>
<option>Angular</option>
<option>React</option>
<option>Nerv</option>
</select>
</div>
</div>
<script type="text/javascript" src="./js/index.js"></script>
<script type="text/javascript" src="./js/data.js"></script>
<script type="text/javascript">
const list = new $List({
data,
parasitifer:'.search-list'
})
</script>
</body>
</html>*{
margin: 0;
padding: 0;
list-style: none;
}
body{
background-color: #f3f5f7;
font-family: '微软雅黑';
}
#wrap{
width: 500px;
margin: 50px auto;
}
.custom-select,.native-select{
margin-bottom: 50px;
text-align: center;
}
#wrap .title{
color: lightblue;
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
display: flex;
justify-content: center;
}
.custom-select .selector {
display: flex;
flex-direction: column;
justify-content: center;
border: 1px solid skyblue;
width: 251px;
margin: 0 auto;
}
.custom-select .selector .selector-content {
height: 25px;
display: flex;
position: relative;
}
.custom-select .selector .selector-content .selector-text{
height: 25px;
flex: 1;
cursor: pointer;
border: none;
text-indent: 5px;
}
.custom-select .selector .selector-content .selector-icon{
width: 50px;
height: 25px;
background-color: skyblue;
line-height: 25px;
text-align: center;
color: #fff;
font-size: 14px;
cursor: pointer;
}
.custom-select .selector .selector-item{
display: none;
width: 250px;
height: 225px;
background-color: #fff;
position: absolute;
top: 123px;
overflow: scroll;
overflow-x: hidden;
border-top: 1px solid #ddd;
}
.custom-select .selector .selector-item .search-btn{
width: 220px;
height: 25px;
}
.custom-select .selector .selector-item .search-list{
margin-top: 10px;
margin-left: 5px;
text-align: left;
}
.custom-select .selector .selector-item .search-list dd{
height: 30px;
line-height: 30px;
padding-left: 15px;
margin-top: 5px;
cursor: pointer;
}
.custom-select .selector .selector-item .search-list dd:hover{
background-color: #f3f5f7;
}
.custom-select .selector .selector-item .search-list .search-title{
font-weight: bold;
font-size: 16px;
}
.custom-select .selector-tip{
margin-top: 25px;
}(function(window,document){
const methods = {
appendChild(parent,...children){
children.forEach(el =>{
parent.appendChild(el);
})
},
$(selector,root = document){
return root.querySelector(selector);
},
$$(selector,root = document){
return root.querySelectorAll(selector);
}
}
let List = function(options){
this._init(options);
this._createList();
this._bind();
this._show();
}
List.prototype._init = function({data,parasitifer}) {
this.types = ['构建工具','前端框架'];//所有分类
this.classified = {'构建工具':[],'前端框架':[]};//分类操作后的数据
this.parasitifer = methods.$(parasitifer);
this._classified(data);
this.shown = false;
this.dl = null;
//console.log(this.classified);
};
List.prototype._classified = function(data) {
data.forEach(({type,value,title})=>{
if(this.types.includes(type)){
this.classified[type].push(title);
}
});
}
List.prototype._createList = function() {
let dl = document.createElement('dl');
let html = '';
for(let i in this.classified){
let dt=`<dt class='search-title'>${i}</dt>`;
let dd = ``;
for(let j in this.classified[i]){
dd +=`<dd>${this.classified[i][j]}</dd>`;
}
html += dt + dd;
}
dl.innerHTML = html;
methods.appendChild(methods.$('.search-list'),dl);
};
List.prototype._changvalue = function() {
let oldValue = methods.$('.old-value');
let newValue = methods.$('.new-value');
};
List.prototype._bind = function(data) {
let selector = methods.$('.selector-contnet');
let text = methods.$('.selector-text');
let arrow = methods.$('.selector-icon')
let item = methods.$('.selector-item');
this.shown = true;
arrow.addEventListener('click',e => {
e.stopPropagation();
if(!this.shown){
this._show(item);
}else{
this._show(item);
}
});
item.addEventListener('click',({target})=>{
if(target.nodeName == 'DD'){
this._show(item);
text.value = target.innerHTML;
}
});
};
List.prototype._show = function() {
let item = methods.$('.selector-item');
if(!this.shown){
item.style.display = 'block';
this.shown = true;
}else{
item.style.display = 'none';
this.shown = false;
}
};
window.$List = List;
})(window,document)const data = [
{
type:'构建工具',
value:1,
title:'Babel'
},
{
type:'构建工具',
value:2,
title:'Webpack'
},
{
type:'构建工具',
value:3,
title:'Rollup'
},
{
type:'前端框架',
value:4,
title:'Vue'
},
{
type:'前端框架',
value:5,
title:'Angular'
},
{
type:'前端框架',
value:6,
title:'React'
},
{
type:'前端框架',
value:7,
title:'Nerv'
}
]1.点击下拉框内容的,会因为冒泡把下拉列表关掉,要怎么阻止这个冒泡?
2.能讲一下搜索框的思路?
19
收起
正在回答
2回答
不是很理解同学说的“点击下拉框内容的,会因为冒泡把下拉列表关掉,要怎么阻止这个冒泡?”是什么意思,建议再详细描述一下,关于搜索框的实现思路,老师这里帮你书写一个案例,同学可以参考在自己的代码基础上进行修改,一步一步自己动手完成要比老师直接给你答案收获的多
参考思路:
<body>
<div id="wrap">
<div>
<div class="title">自定义select</div>
<select id="demo">
<option value="1" type="构建工具">Babel</option>
<option value="2" type="构建工具">Webpack</option>
<option value="3" type="构建工具">Rollup</option>
<option value="4" type="前端框架">Vue</option>
<option value="5" type="前端框架">Angular</option>
<option value="6" type="前端框架">React</option>
<option value="7" type="前端框架">Nerv</option>
</select>
</div>
<div>
<p id="out"></p>
</div>
<div style="text-align: center;margin-top: 60px;">
<div class="title">原生select</div>
<select>
<option value="1" type="构建工具">Babel</option>
<option value="2" type="构建工具">Webpack</option>
<option value="3" type="构建工具">Rollup</option>
<option value="4" type="前端框架">Vue</option>
<option value="5" type="前端框架">Angular</option>
<option value="6" type="前端框架">React</option>
<option value="7" type="前端框架">Nerv</option>
</select>
</div>
</div>
<script src="./main.js"></script>
<script>
let sel = ClassifySelect('#demo', {
onChange(target, before, after) {
document.querySelector('#out').innerText = `
之前的值是 ${ before[0] } - ${ before[1] }
改变后的值是 ${ after[0] } - ${ after[1] }
`
}
});
</script>
</body>function ClassifySelect(selector, { onChange }) {
// 生成每个分类的html字符串
function createOptionList(classify, active) {
// 每一个分类的html字符串
let optionLists = [];
// 遍历分类后的对象
for (let [k, v] of Object.entries(classify)) {
// 用于保存每一个项html字符串
let items = [];
// 便利每个分类下的每一项 生成每一项的html字符串 如果当前需要选中的值和当前遍历到的项的值一样 则加上激活状态下的样式
v.forEach(item => items.push(`
<div class="item${ active === item.value ? ' active' : '' }">
<span value="${ item.value }">${ item.text }</span>
</div>`));
// 生成每一个分类的html字符串
optionLists.push(`
<div class="option-list">
<div class="name">${ k }</div>
${ items.join('') }
</div>
`)
}
return optionLists;
}
// 生成整个下拉框的dom
function createElement(classify, active) {
// 最外层容器
let wrap = document.createElement('div');
wrap.className = 'select';
// 生成每个分类的html字符串
let optionLists = createOptionList(classify, active);
wrap.innerHTML = `
<div class="shown">
<span>未选择</span><button>▼</button>
</div>
<div class="options">
<div class="search">
<input type="text" placeholder="搜索">
</div>
<div class="lists">
${ optionLists.join('') }
</div>
</div>
`;
return wrap;
}
// 用于切换下拉列表的显示
function toggle(el, isShow, onChange = () => {}) {
// 根据是是否显示切换下拉列表的显示与隐藏
options.style.transform = isShow ? 'scale(1, 0) translate(0, -50%)' : 'scale(1, 1) translate(0, 0)';
// 返回新的显示状态
return !isShow;
}
//-------获取原生的select-------------------------------
// 根据传入的选择器 获取原生的select
let el = document.querySelector(selector);
// 获取该select的父节点(div)
let pNode = el.parentNode;
// 根据原生的select中的每一项提供的类型和值进行分类
// 用于存放分类后的结果
let classify = {};
// 获得下拉列表的每一项组成的数组 并遍历(option数组,)function(option)
Array.from(el.querySelectorAll('option')).forEach(option => {
// 获取类型
const type = option.getAttribute('type');
// 判断是否存在该类型 不存在则置为空数组
if (!classify[type]) {
classify[type] = [];
}
// 将每一项放入对应类型的数组
classify[type].push({
// 实际的值
value: option.getAttribute('value'),
// 用于显示的文本
text: option.innerText
});
});
// 分类完毕后根据数据生成dom
// 通过分类后的数据 生成整个下拉框
let wrap = createElement(classify);
// console.log(wrap)
// 用于展示当前选中项的元素
let showText = wrap.querySelector('.shown span');
// 用于放置所有分类的元素
let lists = wrap.querySelector('.options .lists');
// 将新的下拉框放到页面中 然后移除原生的下拉框
// 将新的下拉框插入到当前的原生下拉框之前
pNode.insertBefore(wrap, el);
// 移除原生下拉框
pNode.removeChild(el);
// 获取需要绑定事件 或者需要用到的元素
// 下拉列表不显示时候展示的元素 用于绑定事件
let shown = pNode.querySelector('.shown');
// 下拉列表元素
let options = pNode.querySelector('.options');
// 标识当前时候显示下拉列表
let isShow = false;
// ---------------给下拉框展示当前选中项的部分绑定事件 点击的时候可以切换下拉列表的显示--------------
// 绑定事件 点击的时候切换下拉列表的显示与隐藏
shown.addEventListener('click', e => isShow = toggle(options, isShow));
// 给window绑定事件 点击非下拉框的部分可以收起下拉列表
// 给window绑定事件 当下拉列表处于展示状态的时候 点击非下拉框的元素 则会收起下拉列表
window.addEventListener('click', e => {
// 判断当前点中的是不是下拉框
const i = e.path.findIndex(el => {
if (!el.className) return false;
return el.className.includes('select')
});
// 如果不是 并且下拉列表处于显示状态 则隐藏
if (i === -1 && isShow) {
isShow = toggle(options, isShow)
}
});
// 将每一个选项的点击事件代理到上一层 这样在动态增减选项的时候就不需要重复的绑定事件点击的时候进行修改当前选中的信息 切换选中的样式等操作
// 用于存放当前选中的项
let curSelected = {};
// 存放当前选中的值
let curValue;
// console.log(curValue)
// 给整个下拉列表绑定事件 (将选择选项时候的事件代理到整个下拉列表上)
options.addEventListener('click', ({ target, path }) => {
// 如果点击到的是一个span 则将target变成他的父级元素 这样如果点击到的是每一项下的span 也可以成功点击
if (target.nodeName === 'SPAN') target = target.parentNode;
// 获取当前点击到的元素的className
let classNames = target.className.split(' ');
console.log(classNames)
// 通过class判断点击到的是不是下拉列表的选项 如果不是 则直接返回 不做任何操作
if (!classNames.includes('item')) return;
// 保留当前选中的值
let tpmValue = curValue;
// 判断是不是已经选中的项 如果是 则什么都不做
if (classNames.includes('active')) {
shown.click();
return;
}
// 修改class列表 加上激活情况下的样式
classNames = ['item', 'active'];
// 找到当前选中项并把她的激活样式去掉
// 不直接使用curSelected.className = 'item'是因为 如果在搜索之后 之前有选中项 则样式不会被去掉 因为dom被新的html字符串替代 直接修改不会影响到页面上的dom
[...lists.querySelectorAll('.item')].every(el => {
el.className = 'item';
return !el.className.includes('active');
});
// 将当前选中项重新赋值
curSelected = target;
// 设置当前选中的值
curValue = curSelected.querySelector('span').getAttribute('value');
// 触发 change 事件
onChange(target, [showText.innerText, tpmValue], [target.innerText, curValue]);
// 给当前元素赋值新的class
target.className = classNames.join(' ');
// 当前显示的文本变成当前选中项的文本
showText.innerText = target.innerText;
// 手动触发事件 将下拉列表收起
shown.click();
});
// 给搜索框绑定事件 当内容变化的时候 通过输入的内容对分类进行筛选 得出新的分类 然后生成新的dom
// 给搜索框绑定事件
options.querySelector('.search input').addEventListener('keyup', function(e) {
// 获取当前输入的值 如果这里用箭头函数 this 不会指向 input
let val = this.value.toUpperCase();
// 如果值为空 则显示所有内容
if (val === '') {
lists.innerHTML = createOptionList(classify, curValue).join('');
return;
}
// 复制一份当前的分类后的内容 如果使用扩展运算符或者Object.assign则是潜复制 所以这里使用这种方式
let newClassify = JSON.parse(JSON.stringify(classify));
// 遍历分类
for (let [k, v] of Object.entries(newClassify)) {
// 用于存放新的选项
let arr = [];
// 遍历该分类下的所有选项
v.forEach(item => {
// 如果这个选项符合搜索条件 则放到arr数组中
if (item.text.toUpperCase().includes(val)) arr.push(item);
});
// 将当前的这个分类的选项赋值为筛选后的选项
newClassify[k] = arr;
}
// 修改显示的内容
lists.innerHTML = createOptionList(newClassify, curValue).join('');
});
// 给实例提供一些可以获取内部值的方法
// 实例可以调用的两个属性
return {
// 通过get方法 当在实例对象上访问value的时候 可以获得当前选中的值
get value() {
return curValue;
},
// 通过get方法 当在实例对象上访问text的时候 可以获得当显示的内容
get text() {
return curSelected.innerText;
}
}
}希望可以帮到你!
相似问题
登录后可查看更多问答,登录/注册
4.Vue与React高级框架开发
- 参与学习 人
- 提交作业 239 份
- 解答问题 10739 个
本阶段带你深入前端开发的肌理,通过ES6基础知识和前端主流高级框架的学习,助你快速构建企业级移动webAPP应用,进入职场的终极battle
了解课程

恭喜解决一个难题,获得1积分~
来为老师/同学的回答评分吧
0 星