You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现输入@时显示用户匹配内联建议的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:26:38