如何实现输入@时显示用户匹配内联建议的textarea组件
嘿,你说的这个功能业内一般叫**@提及系统(@Mention System)**,就是社交平台、协作工具里常用的“@某人”触发用户匹配的功能,我来给你拆解下实现的核心步骤和关键细节:
核心实现步骤
1. 监听输入,捕捉@触发时机
首先得实时监控textarea的输入,判断用户是不是正在输入@后的内容。核心是要跟踪光标位置,因为用户可能在文本中间插入@,不是只在末尾输入。
举个简单的监听逻辑:
const textarea = document.getElementById('mention-textarea'); const suggestionBox = document.getElementById('suggestion-box'); let isMentioning = false; let currentMentionQuery = ''; textarea.addEventListener('input', (e) => { const cursorPos = textarea.selectionStart; const textBeforeCursor = textarea.value.substring(0, cursorPos); // 检查光标前是否有@,且@后面没有空格(说明正在输入用户名) const mentionMatch = textBeforeCursor.match(/@(\w*)$/); if (mentionMatch) { isMentioning = true; currentMentionQuery = mentionMatch[1]; // 触发用户匹配 showSuggestions(currentMentionQuery); } else { isMentioning = false; suggestionBox.style.display = 'none'; } // 调整建议框位置到光标下方 positionSuggestionBox(cursorPos); });
2. 实时匹配用户,展示内联建议
当检测到用户正在输入@后的关键词时,需要从用户列表里过滤出匹配的结果,然后把建议框定位到光标对应的位置。
匹配逻辑
const userList = [ { id: 1, name: 'Alice Smith' }, { id: 2, name: 'Bob Johnson' }, { id: 3, name: 'Charlie Brown' } ]; function showSuggestions(query) { const matchedUsers = userList.filter(user => user.name.toLowerCase().includes(query.toLowerCase()) ); if (matchedUsers.length === 0) { suggestionBox.style.display = 'none'; return; } // 渲染建议列表 suggestionBox.innerHTML = matchedUsers.map(user => `<div class="suggestion-item" data-id="${user.id}">${user.name}</div>` ).join(''); suggestionBox.style.display = 'block'; }
定位建议框
要让建议框精准出现在光标下方,需要计算光标在textarea中的坐标:
function positionSuggestionBox(cursorPos) { // 创建一个临时span来计算光标位置 const tempSpan = document.createElement('span'); tempSpan.textContent = textarea.value.substring(0, cursorPos); tempSpan.style.position = 'absolute'; tempSpan.style.visibility = 'hidden'; tempSpan.style.font = window.getComputedStyle(textarea).font; document.body.appendChild(tempSpan); const rect = tempSpan.getBoundingClientRect(); const textareaRect = textarea.getBoundingClientRect(); // 设置建议框位置 suggestionBox.style.position = 'absolute'; suggestionBox.style.top = `${rect.bottom - textareaRect.top + textarea.scrollTop}px`; suggestionBox.style.left = `${rect.left - textareaRect.left + textarea.scrollLeft}px`; document.body.removeChild(tempSpan); }
3. 处理建议选择,插入到文本中
当用户点击建议项(或用键盘选择)时,要把选中的用户名替换掉@后面的输入内容,同时调整光标位置:
suggestionBox.addEventListener('click', (e) => { if (!e.target.classList.contains('suggestion-item')) return; const selectedName = e.target.textContent; const cursorPos = textarea.selectionStart; const textBeforeCursor = textarea.value.substring(0, cursorPos); // 找到@开始的位置 const mentionStartIndex = textBeforeCursor.lastIndexOf('@'); // 替换@后的内容为选中的用户名,加空格方便继续输入 const newText = textarea.value.substring(0, mentionStartIndex) + `@${selectedName} ` + textarea.value.substring(cursorPos); textarea.value = newText; // 把光标移到插入内容的后面 textarea.selectionStart = textarea.selectionEnd = mentionStartIndex + selectedName.length + 2; suggestionBox.style.display = 'none'; isMentioning = false; });
4. 体验优化细节
- 键盘导航:支持上下箭头切换建议项,回车键确认,这样用户不用鼠标也能操作
- 退格处理:如果用户删除@,自动隐藏建议框;如果删除@后的部分,重新触发匹配
- 重复提及过滤:可以记录已经提及的用户ID,在匹配时排除,避免重复@
- 多行适配:如果textarea是多行的,要确保建议框不会被滚动条挡住,或者随textarea滚动调整位置
内容的提问来源于stack exchange,提问作者Arun Jose
相关产品推荐
相关产品推荐

