超实用!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>
相关推荐
李长渊哦2 小时前
深入理解 JavaScript 中的全局对象与 JSON 序列化
开发语言·javascript·json
Senar4 小时前
如何判断浏览器是否开启硬件加速
前端·javascript·数据可视化
codingandsleeping4 小时前
一个简易版无缝轮播图的实现思路
前端·javascript·css
拉不动的猪6 小时前
简单回顾下插槽透传
前端·javascript·面试
爱吃鱼的锅包肉6 小时前
Flutter路由模块化管理方案
前端·javascript·flutter
风清扬雨6 小时前
Vue3具名插槽用法全解——从零到一的详细指南
前端·javascript·vue.js
海盗强7 小时前
Vue 3 常见的通信方式
javascript·vue.js·ecmascript
oscar9998 小时前
JavaScript与TypeScript
开发语言·javascript·typescript
橘子味的冰淇淋~8 小时前
【解决】Vue + Vite + TS 配置路径别名成功仍爆红
前端·javascript·vue.js
利刃之灵8 小时前
03-HTML常见元素
前端·html