移动端Chip组件展开/更多功能实现咨询(无第三方UI库)
解决方案
核心思路
放弃单纯依赖CSS flex + overflow:hidden 的方案,改用JS动态计算可视区域可容纳的Chip数量,主动隐藏超出部分,并在末尾显示「+N more」按钮,点击按钮弹出底部面板展示所有选中项。
实现代码
HTML结构
<!-- Autocomplete 输入框 --> <input type="text" class="autocomplete-input" placeholder="输入选项"> <!-- Chip 展示容器 --> <div class="chip-container"> <!-- 可视Chip容器 --> <div class="visible-chips"></div> <!-- 剩余Chip容器(默认隐藏) --> <div class="hidden-chips"></div> <!-- More按钮(默认隐藏) --> <button class="more-btn">+<span class="more-count">0</span> more</button> </div> <!-- 底部面板(默认隐藏) --> <div class="bottom-panel"> <div class="panel-header"> <h3>所有选中项</h3> <button class="close-panel">×</button> </div> <div class="panel-chips"></div> </div> <!-- 遮罩层 --> <div class="overlay"></div>
CSS样式
/* 全局基础样式 */ * { margin: 0; padding: 0; box-sizing: border-box; font-family: sans-serif; } /* Chip容器 */ .chip-container { display: flex; flex-wrap: nowrap; gap: 8px; padding: 8px; border: 1px solid #eee; border-radius: 4px; margin-top: 8px; overflow: visible; /* 取消overflow:hidden,避免裁剪more按钮 */ } /* 单个Chip样式 */ .chip { display: inline-flex; align-items: center; padding: 4px 8px; background: #e0e0e0; border-radius: 16px; font-size: 14px; gap: 4px; } .chip .remove-btn { cursor: pointer; font-size: 16px; line-height: 1; } /* 可视Chip容器 */ .visible-chips { display: flex; flex-wrap: nowrap; gap: 8px; } /* 剩余Chip容器 */ .hidden-chips { display: none; } /* More按钮 */ .more-btn { display: none; padding: 4px 8px; border: none; background: #f5f5f5; border-radius: 16px; cursor: pointer; font-size: 14px; } /* 移动端适配(以768px为分界) */ @media (max-width: 768px) { .chip-container { flex-wrap: nowrap; overflow: visible; min-width: 0; } .visible-chips { flex-wrap: nowrap; overflow: hidden; min-width: 0; } } /* 底部面板样式 */ .bottom-panel { position: fixed; bottom: -100%; left: 0; width: 100%; background: #fff; border-top-left-radius: 16px; border-top-right-radius: 16px; box-shadow: 0 -2px 10px rgba(0,0,0,0.1); transition: bottom 0.3s ease; max-height: 70vh; overflow-y: auto; } .bottom-panel.open { bottom: 0; } .panel-header { display: flex; justify-content: space-between; align-items: center; padding: 16px; border-bottom: 1px solid #eee; } .close-panel { border: none; background: none; font-size: 24px; cursor: pointer; } .panel-chips { display: flex; flex-wrap: wrap; gap: 8px; padding: 16px; } /* 遮罩层 */ .overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: none; } .overlay.open { display: block; }
JavaScript逻辑
// 模拟选中的Chip数据(最多10个) let selectedChips = ['选项1', '选项2', '选项3', '选项4', '选项5', '选项6', '选项7']; // DOM元素 const visibleChipsEl = document.querySelector('.visible-chips'); const hiddenChipsEl = document.querySelector('.hidden-chips'); const moreBtnEl = document.querySelector('.more-btn'); const moreCountEl = document.querySelector('.more-count'); const bottomPanelEl = document.querySelector('.bottom-panel'); const panelChipsEl = document.querySelector('.panel-chips'); const overlayEl = document.querySelector('.overlay'); const closePanelBtn = document.querySelector('.close-panel'); // 初始化渲染 renderChips(); // 窗口 resize 时重新计算 window.addEventListener('resize', renderChips); // 渲染Chip函数 function renderChips() { // 清空容器 visibleChipsEl.innerHTML = ''; hiddenChipsEl.innerHTML = ''; panelChipsEl.innerHTML = ''; // 生成所有Chip的DOM const chipElements = selectedChips.map(text => createChipElement(text)); // 渲染面板内的所有Chip chipElements.forEach(el => panelChipsEl.appendChild(el.cloneNode(true))); // 移动端计算可视数量 if (window.innerWidth <= 768) { const containerWidth = document.querySelector('.chip-container').offsetWidth; const moreBtnWidth = moreBtnEl.offsetWidth; let totalWidth = 0; let visibleCount = 0; // 计算能容纳的Chip数量 for (let i = 0; i < chipElements.length; i++) { const chipWidth = chipElements[i].offsetWidth; // 预留间隔和more按钮的宽度 if (totalWidth + chipWidth + 8 + moreBtnWidth <= containerWidth) { totalWidth += chipWidth + 8; visibleChipsEl.appendChild(chipElements[i]); visibleCount++; } else { hiddenChipsEl.appendChild(chipElements[i]); } } // 显示/隐藏more按钮 const hiddenCount = chipElements.length - visibleCount; if (hiddenCount > 0) { moreBtnEl.style.display = 'block'; moreCountEl.textContent = hiddenCount; } else { moreBtnEl.style.display = 'none'; } } else { // 桌面端显示所有Chip(最多10个) chipElements.forEach(el => visibleChipsEl.appendChild(el)); moreBtnEl.style.display = 'none'; } } // 创建单个Chip元素 function createChipElement(text) { const chip = document.createElement('div'); chip.className = 'chip'; chip.innerHTML = ` <span>${text}</span> <span class="remove-btn">×</span> `; // 绑定删除事件 chip.querySelector('.remove-btn').addEventListener('click', () => { selectedChips = selectedChips.filter(item => item !== text); renderChips(); }); return chip; } // 打开底部面板 moreBtnEl.addEventListener('click', () => { bottomPanelEl.classList.add('open'); overlayEl.classList.add('open'); }); // 关闭底部面板 function closePanel() { bottomPanelEl.classList.remove('open'); overlayEl.classList.remove('open'); } closePanelBtn.addEventListener('click', closePanel); overlayEl.addEventListener('click', closePanel);
关键说明
- 避免裁剪问题:取消了Chip容器的
overflow:hidden,改用给visible-chips容器设置overflow:hidden,确保more按钮始终能完整显示。 - 动态计算适配:通过JS获取容器宽度、more按钮宽度和每个Chip的宽度,精确计算可视区域能容纳的Chip数量,剩余的放入隐藏容器。
- 交互逻辑:点击more按钮弹出底部面板展示所有选中项,点击遮罩或关闭按钮收起面板,同时支持Chip的删除操作。
内容的提问来源于stack exchange,提问作者JalajYadav_Avesta
相关产品推荐
相关产品推荐

