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

基于JavaScript的textarea@提及系统:高亮与建议列表异常排查

解决textarea中@提及系统的两个问题:文本高亮与建议列表定位

一、@文本未高亮的解决方案

textarea是纯文本输入控件,无法直接对部分文本应用样式。我们需要用叠加层div模拟高亮效果,具体实现:

  • 创建与textarea样式完全一致的div,通过绝对定位覆盖在textarea上方,设置背景透明,仅对@开头的文本添加蓝色样式
  • 监听textarea的输入/滚动事件,同步内容到叠加层,并将所有@开头的匹配文本用带样式的span包裹
  • 同步textarea和叠加层的滚动状态,避免视觉错位

二、建议列表未在光标旁显示的解决方案

要让列表精准定位在光标旁,需计算光标在textarea中的绝对坐标:

  • 监听textarea的input或keydown事件,检测到输入@时,通过Selection API获取光标对应的文本范围
  • 用getBoundingClientRect()计算该范围的屏幕坐标,结合textarea的页面位置,动态设置建议列表的top和left样式
  • 调整偏移量确保列表不会遮挡光标

完整实现代码

HTML

<div class="mention-container">
  <textarea id="mentionTextarea" placeholder="输入@试试..."></textarea>
  <div id="highlightOverlay"></div>
  <ul id="suggestionList" class="suggestion-list">
    <li>Alice</li>
    <li>Bob</li>
    <li>Charlie</li>
  </ul>
</div>

CSS

.mention-container {
  position: relative;
  width: 500px;
  height: 200px;
}

#mentionTextarea, #highlightOverlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  padding: 10px;
  font-size: 16px;
  font-family: Arial, sans-serif;
  line-height: 1.5;
  border: 1px solid #ccc;
  border-radius: 4px;
  white-space: pre-wrap;
  word-wrap: break-word;
}

#mentionTextarea {
  background: transparent;
  z-index: 1;
  resize: none;
}

#highlightOverlay {
  color: transparent;
  z-index: 0;
  pointer-events: none; /* 让点击事件穿透到textarea */
}

.highlight-mention {
  color: #1a73e8; /* 蓝色高亮 */
}

.suggestion-list {
  position: absolute;
  display: none;
  margin: 0;
  padding: 0;
  list-style: none;
  background: white;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.15);
  z-index: 2;
  width: 150px;
}

.suggestion-list li {
  padding: 8px 12px;
  cursor: pointer;
}

.suggestion-list li:hover {
  background: #f1f3f4;
}

JavaScript

const textarea = document.getElementById('mentionTextarea');
const highlightOverlay = document.getElementById('highlightOverlay');
const suggestionList = document.getElementById('suggestionList');

// 同步文本内容与高亮样式
function syncHighlight() {
  let content = textarea.value;
  // 匹配所有@开头的单词(支持字母、数字、下划线)
  content = content.replace(/@(\w+)/g, '<span class="highlight-mention">@$1</span>');
  highlightOverlay.innerHTML = content;
  // 同步滚动条位置
  highlightOverlay.scrollTop = textarea.scrollTop;
}

// 获取光标在textarea内的坐标
function getCursorPosition() {
  const selection = window.getSelection();
  if (selection.rangeCount === 0) return null;
  
  const range = selection.getRangeAt(0);
  const rect = range.getBoundingClientRect();
  const containerRect = textarea.getBoundingClientRect();
  
  return {
    top: rect.top - containerRect.top + textarea.scrollTop + 20, // 偏移20px避免遮挡光标
    left: rect.left - containerRect.left + textarea.scrollLeft
  };
}

// 显示建议列表并定位到光标旁
function showSuggestions() {
  const pos = getCursorPosition();
  if (!pos) return;
  
  suggestionList.style.display = 'block';
  suggestionList.style.top = `${pos.top}px`;
  suggestionList.style.left = `${pos.left}px`;
}

// 隐藏建议列表
function hideSuggestions() {
  suggestionList.style.display = 'none';
}

// 监听输入事件
textarea.addEventListener('input', () => {
  syncHighlight();
  const lastChar = textarea.value.slice(-1);
  if (lastChar === '@') {
    showSuggestions();
  } else {
    // 若输入内容不在@开头的单词范围内,隐藏列表
    const mentionRegex = /@\w*$/;
    if (!mentionRegex.test(textarea.value)) {
      hideSuggestions();
    }
  }
});

// 监听滚动事件,同步高亮层滚动
textarea.addEventListener('scroll', syncHighlight);

// 点击建议项插入提及文本
suggestionList.addEventListener('click', (e) => {
  if (e.target.tagName === 'LI') {
    const mentionText = `@${e.target.textContent} `;
    const cursorPos = textarea.selectionStart;
    const currentValue = textarea.value;
    // 替换@为完整提及文本
    const newValue = currentValue.slice(0, cursorPos - 1) + mentionText + currentValue.slice(cursorPos);
    textarea.value = newValue;
    // 将光标移至提及文本末尾
    textarea.selectionStart = textarea.selectionEnd = cursorPos - 1 + mentionText.length;
    syncHighlight();
    hideSuggestions();
  }
});

// 初始化高亮状态
syncHighlight();

内容的提问来源于stack exchange,提问作者mürtaza güler

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 20:03:16