超实用!HTML&CSS&JS 下拉多选功能解析,建议收藏

在前端开发过程中,我们常常会遇到一些看似常规却又充满挑战的需求。最近我就接到了一个任务,要求在页面中实现一个下拉选择支持多选的功能。起初,我考虑使用原生的 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>
相关推荐
yqcoder19 分钟前
electron 打包后的 exe 文件,运行后是空白窗口
前端·javascript·electron
Jiaberrr22 分钟前
微信小程序:跨页面数据修改全攻略
前端·javascript·微信小程序·小程序
曹天骄29 分钟前
next-auth v5 结合 Prisma 实现登录与会话管理
javascript
jackispy34 分钟前
JS宏进阶: 工厂函数与构造函数
java·开发语言·javascript
黑客KKKing41 分钟前
网安-HTML
前端·后端·学习·安全·html
小马哥编程44 分钟前
Word中如何格式化与网页和 HTML 内容相关的元素
前端·html
weitao_111 小时前
使用opencv.js 的时候报错 Uncaught 1022911432
前端·javascript
℡52Hz★1 小时前
Three.js+Vue3+Vite应用lil-GUI调试开发3D效果(三)
开发语言·前端·javascript·3d
栀椩2 小时前
electron编写一个macOS风格的桌面应用
javascript·macos·electron
天若有情6733 小时前
想法分享,利用html通过求输入框中用户输入数组的最大值
前端·html