实现类似Upwork技能栏的多语言标签选择功能求助
嘿,你已经搭好了基础框架,接下来我们把它改造成Upwork那种丝滑的标签式多选组件!我帮你梳理了完整的实现方案,包括输入联想、标签管理和交互优化,直接上手就能用:
核心功能实现思路
我们需要在现有下拉复选框的基础上,补充三个关键能力:
- 实时输入联想:监听用户输入,动态过滤匹配的语言选项
- 标签化管理:将选中的语言转为可删除的标签,支持多选不重复
- 流畅交互:支持点击选项/回车添加标签、删除标签同步更新选项状态
完整代码实现
HTML 结构(替换你现有的按钮组)
<div class="language-selector"> <!-- 已选标签容器 --> <div class="selected-tags"></div> <!-- 用户输入框 --> <input type="text" class="language-input" placeholder="输入语言名称..." /> <!-- 联想下拉列表 --> <ul class="suggestion-list"> <li><label><input type="checkbox" value="JavaScript" /> JavaScript</label></li> <li><label><input type="checkbox" value="Python" /> Python</label></li> <li><label><input type="checkbox" value="Java" /> Java</label></li> <li><label><input type="checkbox" value="C++" /> C++</label></li> <li><label><input type="checkbox" value="TypeScript" /> TypeScript</label></li> <li><label><input type="checkbox" value="Go" /> Go</label></li> </ul> </div>
CSS 样式(贴近Upwork视觉风格)
.language-selector { position: relative; width: 450px; border: 1px solid #e1e5e9; border-radius: 6px; padding: 8px; display: flex; flex-wrap: wrap; gap: 6px; background: white; } .selected-tags .tag { background: #e6f0ff; color: #2563eb; padding: 4px 10px; border-radius: 20px; font-size: 14px; display: flex; align-items: center; gap: 6px; } .selected-tags .tag .remove-btn { cursor: pointer; font-size: 16px; line-height: 1; } .language-input { border: none; outline: none; flex: 1; min-width: 150px; padding: 4px 0; font-size: 14px; } .suggestion-list { position: absolute; top: calc(100% + 2px); left: 0; right: 0; border: 1px solid #e1e5e9; border-radius: 0 0 6px 6px; background: white; list-style: none; padding: 0; margin: 0; max-height: 220px; overflow-y: auto; box-shadow: 0 4px 12px rgba(0,0,0,0.05); display: none; } .suggestion-list.show { display: block; } .suggestion-list li { padding: 10px 12px; cursor: pointer; font-size: 14px; } .suggestion-list li:hover { background: #f8fafc; } .suggestion-list input[type="checkbox"] { margin-right: 8px; cursor: pointer; }
JavaScript 交互逻辑
const languageInput = document.querySelector('.language-input'); const suggestionList = document.querySelector('.suggestion-list'); const selectedTagsContainer = document.querySelector('.selected-tags'); const allSuggestionItems = Array.from(suggestionList.querySelectorAll('li')); const selectedLanguages = new Set(); // 用Set避免重复选中 // 1. 实时输入过滤联想列表 languageInput.addEventListener('input', (e) => { const query = e.target.value.toLowerCase().trim(); suggestionList.classList.add('show'); allSuggestionItems.forEach(item => { const languageName = item.textContent.trim().toLowerCase(); const checkbox = item.querySelector('input'); // 显示匹配的选项,同步已选中状态 if (languageName.includes(query)) { item.style.display = 'block'; checkbox.checked = selectedLanguages.has(checkbox.value); } else { item.style.display = 'none'; } }); }); // 2. 点击联想选项添加/移除标签 suggestionList.addEventListener('click', (e) => { const targetLi = e.target.closest('li'); if (!targetLi) return; const checkbox = targetLi.querySelector('input'); const language = checkbox.value; if (selectedLanguages.has(language)) { removeTag(language); checkbox.checked = false; } else { addTag(language); checkbox.checked = true; } languageInput.value = ''; // 清空输入框 }); // 3. 回车快速添加第一个匹配的标签 languageInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); // 找到第一个显示且未选中的选项 const targetItem = allSuggestionItems.find(item => { const checkbox = item.querySelector('input'); return item.style.display !== 'none' && !selectedLanguages.has(checkbox.value); }); if (targetItem) { const checkbox = targetItem.querySelector('input'); addTag(checkbox.value); checkbox.checked = true; languageInput.value = ''; } } }); // 4. 点击页面其他区域隐藏联想列表 document.addEventListener('click', (e) => { if (!e.target.closest('.language-selector')) { suggestionList.classList.remove('show'); } }); // 辅助函数:添加标签 function addTag(language) { if (selectedLanguages.has(language)) return; selectedLanguages.add(language); const tagElement = document.createElement('div'); tagElement.className = 'tag'; tagElement.dataset.language = language; tagElement.innerHTML = ` ${language} <span class="remove-btn">×</span> `; // 绑定标签删除事件 tagElement.querySelector('.remove-btn').addEventListener('click', () => { removeTag(language); }); selectedTagsContainer.appendChild(tagElement); } // 辅助函数:移除标签 function removeTag(language) { selectedLanguages.delete(language); const tagElement = selectedTagsContainer.querySelector(`[data-language="${language}"]`); if (tagElement) tagElement.remove(); // 同步更新复选框状态 const checkbox = suggestionList.querySelector(`input[value="${language}"]`); if (checkbox) checkbox.checked = false; }
关键细节说明
- 用
Set存储已选语言,天然避免重复选中的问题 - 输入联想时实时过滤选项,同时同步已选中的状态
- 支持两种添加标签的方式:点击选项或回车快速选择第一个匹配项
- 删除标签时自动同步下拉列表的复选框状态,保证交互一致性
内容的提问来源于stack exchange,提问作者A.A. F
相关产品推荐
相关产品推荐

