在前端开发过程中,我们常常会遇到一些看似常规却又充满挑战的需求。最近我就接到了一个任务,要求在页面中实现一个下拉选择支持多选的功能。起初,我考虑使用原生的 select 标签,但在实际操作中发现其默认的 UI 无法满足项目的设计需求,因此决定使用原生 JavaScript 来打造这个功能。以下是我整个开发过程的详细记录。
一、需求分析
项目需要一个能够让用户从多个选项中选择多个变量的下拉菜单。用户选择的变量需要实时显示在输入框中,并且在页面刷新后,用户之前的选择能够被保留。这意味着我们不仅要实现多选的交互逻辑,还要处理数据的存储和读取。
二、HTML 结构搭建
首先,我创建了一个基本的 HTML 结构。使用一个 div 容器作为整个下拉选择组件的外壳,并赋予其 container 类,用于设置样式和布局。在容器内部,有一个标题 h1 用于描述该组件的功能。 接着,创建了一个 select-container 类的 div 作为下拉选择的容器。在这个容器中,select-input 类的 div 用于显示当前选择的变量或提示信息,并且包含一个 input 元素用于显示内容,以及一个 span 元素用于显示下拉箭头。select-options 类的 div 则用于存放所有的可选变量,初始状态下是隐藏的。
javascript
<div class="container">
<h1>下拉选择支持多选</h1>
<div class="select-container">
<div class="select-input">
<input type="text" id="selectedVariablesInput" placeholder="请选择变量" readonly>
<span class="select-arrow"></span>
</div>
<div class="select-options">
</div>
</div>
</div>
三、CSS 样式设计
为了让组件看起来美观且易于使用,我使用 CSS 进行了样式设计。设置了页面的整体背景颜色、字体等基本样式。对于 container 类,设置了最大宽度、高度、边距、内边距、背景颜色、阴影和圆角,使其在页面中居中显示且具有立体感。 对于 select-input,设置了其显示为弹性布局,设置了边框、圆角、内边距和背景颜色,使其看起来像一个输入框。其中的 input 元素去除了默认边框和轮廓,设置了内边距、字体大小和颜色,使其与外层 div 融合且文字清晰可读。select-arrow 则通过设置三角形的样式来模拟下拉箭头。 select-options 在默认状态下是隐藏的,通过设置 opacity 和 transform 属性实现了显示和隐藏的动画效果。同时,设置了其最大高度、溢出属性、边框、圆角、阴影等,使其在显示时具有良好的视觉效果。每个选项 label 设置了内边距、高度、行高和颜色,并且在鼠标悬停时改变背景颜色以提供交互反馈。
css
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
margin: 0;
padding: 0;
}
.container {
max-width: 300px;
height: 400px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 5px;
margin-top: 50px;
}
h1 {
text-align: center;
color: #333;
font-size: 24px;
}
.select-container {
position: relative;
}
.select-input {
display: flex;
align-items: center;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 0 10px;
cursor: pointer;
background-color: #fff;
}
.select-input input {
flex: 1;
border: none;
outline: none;
padding: 10px 0;
font-size: 14px;
cursor: pointer;
color: #555;
}
.select-arrow {
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid #c0c4cc;
margin-left: 10px;
}
.select-options {
position: absolute;
top: calc(100% + 10px);
left: 0;
width: 100%;
background-color: #fff;
z-index: 1000;
max-height: 200px;
overflow-y: auto;
display: none;
opacity: 0;
transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out;
transform: translateY(10px);
border: 1px solid #e4e7ed;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
box-sizing: border-box;
margin: 5px 0;
padding: 10px 0;
scrollbar-width: none;
-ms-overflow-style: none;
}
.select-options::-webkit-scrollbar {
width: 0;
height: 0;
}
.select-options.show {
display: block;
opacity: 1;
transform: translateY(0);
}
.select-options label {
display: flex;
align-items: center;
padding: 0 20px;
cursor: pointer;
height: 34px;
line-height: 34px;
color: #555;
}
.select-options label:hover {
background-color: #f5f7fa;
}
.select-options input[type="checkbox"] {
margin-right: 10px;
}
.selected-variable {
color: #409eff !important;
}
四、JavaScript 交互逻辑实现
1、生成选项列表: 使用 JavaScript 动态生成了 100 个变量选项。通过 Array.from 方法创建一个包含 100 个元素的数组,每个元素代表一个变量。然后遍历这个数组,为每个变量创建一个 label 元素和一个 input 元素(类型为 checkbox),将它们添加到 select-options 容器中。
ini
const variables = Array.from({ length: 100 }, (_, i) => `变量 ${i + 1}`);
const selectOptions = document.querySelector('.select - options');
variables.forEach(variable => {
const label = document.createElement('label');
const input = document.createElement('input');
input.type = 'checkbox';
input.value = variable;
input.classList.add('variable - checkbox');
label.appendChild(input);
label.appendChild(document.createTextNode(variable));
selectOptions.appendChild(label);
});
2、读取和回显选择状态: 从本地缓存 localStorage 中读取之前保存的选择状态。如果存在保存的变量,遍历所有的复选框,将已选中的变量对应的复选框设置为 checked 状态,并为其 label 添加 selected-variable 类,以显示选中的样式。同时,调用 updateSelectedVariablesInput 函数更新输入框的内容。
ini
const savedVariables = JSON.parse(localStorage.getItem('selectedVariables')) || [];
selectOptions.querySelectorAll('.variable - checkbox').forEach(input => {
if (savedVariables.includes(input.value)) {
input.checked = true;
input.parentNode.classList.add('selected - variable');
}
});
updateSelectedVariablesInput();
3、更新输入框内容: 定义了 updateSelectedVariablesInput 函数,该函数获取所有被选中的变量的值,并将它们以逗号分隔的形式显示在输入框中。如果没有选中任何变量,则显示提示信息。同时,将当前的选择状态保存到本地缓存 localStorage 中。
ini
function updateSelectedVariablesInput() {
const selectedVariables = Array.from(selectOptions.querySelectorAll('.variable - checkbox:checked'))
.map(input => input.value);
selectedVariablesInput.value = selectedVariables.join(', ') || '请选择变量';
localStorage.setItem('selectedVariables', JSON.stringify(selectedVariables));
}
4、切换选项列表显示状态: 为 select-input 添加点击事件监听器。当点击 select-input 时,如果选项列表当前是显示状态,则隐藏它;如果是隐藏状态,则显示它。
javascript
const selectInput = document.querySelector('.select-input');
selectInput.addEventListener('click', function () {
if (selectOptions.classList.contains('show')) {
selectOptions.classList.remove('show');
} else {
selectOptions.classList.add('show');
}
});
5、隐藏选项列表: 为整个文档添加点击事件监听器。当点击页面上除了 select - input 和 select - options 之外的其他地方时,隐藏选项列表。
csharp
document.addEventListener('click', function (event) {
if (!selectInput.contains(event.target) &&!selectOptions.contains(event.target)) {
selectOptions.classList.remove('show');
}
});
6、监听复选框变化: 为 select - options 添加 change 事件监听器。当复选框的状态发生改变时,根据其是否被选中,为对应的 label 添加或移除 selected - variable 类,并调用 updateSelectedVariablesInput 函数更新输入框内容和本地缓存。
csharp
selectOptions.addEventListener('change', function (event) {
if (event.target.type === 'checkbox') {
const label = event.target.parentNode;
if (event.target.checked) {
label.classList.add('selected-variable');
} else {
label.classList.remove('selected-variable');
}
updateSelectedVariablesInput();
}
});
五、总结
起初,面对下拉多选功能的需求,或许大家和我一样,第一反应是依赖现成的 UI 框架。但当我决定尝试用原生 JavaScript 去实现时,才真正打开了前端开发的新世界大门。对于刚入门的前端小白来说,不要害怕尝试原生开发,这能极大地提升我们对前端技术的掌握程度。遇到问题时,多查阅资料,不断调试,每一次解决问题都是一次成长。相信大家在未来的项目中,只要积累足够的经验,都能高效、出色地完成任务,创造出优秀的前端应用。
源代码
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>下拉选择支持多选</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
margin: 0;
padding: 0;
}
.container {
max-width: 300px;
height: 400px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 5px;
margin-top: 50px;
}
h1 {
text-align: center;
color: #333;
font-size: 24px;
}
.select-container {
position: relative;
}
.select-input {
display: flex;
align-items: center;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 0 10px;
cursor: pointer;
background-color: #fff;
}
.select-input input {
flex: 1;
border: none;
outline: none;
padding: 10px 0;
font-size: 14px;
cursor: pointer;
color: #555;
}
.select-arrow {
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid #c0c4cc;
margin-left: 10px;
}
.select-options {
position: absolute;
top: calc(100% + 10px);
left: 0;
width: 100%;
background-color: #fff;
z-index: 1000;
max-height: 200px;
overflow-y: auto;
display: none;
opacity: 0;
transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out;
transform: translateY(10px);
border: 1px solid #e4e7ed;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
box-sizing: border-box;
margin: 5px 0;
padding: 10px 0;
scrollbar-width: none;
-ms-overflow-style: none;
}
.select-options::-webkit-scrollbar {
width: 0;
height: 0;
}
.select-options.show {
display: block;
opacity: 1;
transform: translateY(0);
}
.select-options label {
display: flex;
align-items: center;
padding: 0 20px;
cursor: pointer;
height: 34px;
line-height: 34px;
color: #555;
}
.select-options label:hover {
background-color: #f5f7fa;
}
.select-options input[type="checkbox"] {
margin-right: 10px;
}
.selected-variable {
color: #409eff !important;
}
</style>
</head>
<body>
<div class="container">
<h1>下拉选择支持多选</h1>
<div class="select-container">
<div class="select-input">
<input type="text" id="selectedVariablesInput" placeholder="请选择变量" readonly>
<span class="select-arrow"></span>
</div>
<div class="select-options">
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const variables = Array.from({ length: 100 }, (_, i) => `变量 ${i + 1}`);
const selectInput = document.querySelector('.select-input');
const selectedVariablesInput = document.getElementById('selectedVariablesInput');
const selectOptions = document.querySelector('.select-options');
// 动态生成变量选择列表
variables.forEach(variable => {
const label = document.createElement('label');
const input = document.createElement('input');
input.type = 'checkbox';
input.value = variable;
input.classList.add('variable-checkbox');
label.appendChild(input);
label.appendChild(document.createTextNode(variable));
selectOptions.appendChild(label);
});
// 从本地缓存中读取之前保存的选择状态并回显到输入框
const savedVariables = JSON.parse(localStorage.getItem('selectedVariables')) || [];
selectOptions.querySelectorAll('.variable-checkbox').forEach(input => {
if (savedVariables.includes(input.value)) {
input.checked = true;
input.parentNode.classList.add('selected-variable');
}
});
updateSelectedVariablesInput();
// 更新输入框内容的函数
function updateSelectedVariablesInput() {
const selectedVariables = Array.from(selectOptions.querySelectorAll('.variable-checkbox:checked'))
.map(input => input.value);
selectedVariablesInput.value = selectedVariables.join(', ') || '请选择变量';
// 直接存储选择到本地缓存
localStorage.setItem('selectedVariables', JSON.stringify(selectedVariables));
}
// 点击输入框切换选项列表的显示状态
selectInput.addEventListener('click', function () {
if (selectOptions.classList.contains('show')) {
selectOptions.classList.remove('show');
} else {
selectOptions.classList.add('show');
}
});
// 点击页面其他地方隐藏选项列表
document.addEventListener('click', function (event) {
if (!selectInput.contains(event.target) && !selectOptions.contains(event.target)) {
selectOptions.classList.remove('show');
}
});
// 监听复选框的变化并更新输入框内容及本地缓存
selectOptions.addEventListener('change', function (event) {
if (event.target.type === 'checkbox') {
const label = event.target.parentNode;
if (event.target.checked) {
label.classList.add('selected-variable');
} else {
label.classList.remove('selected-variable');
}
updateSelectedVariablesInput();
}
});
});
</script>
</body>
</html>