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

如何在含多HTML标签的contenteditable div中替换光标前@文本

解决Contenteditable中@提及替换重复的问题

我明白你遇到的问题了——当选中匹配的联系人TEST时,原来输入的@te没被删掉,导致变成了@teTEST。核心原因是你的代码只做了插入新标签的操作,却没有定位并删除光标前那段触发匹配的@开头文本。下面是具体的解决思路和修改后的代码:

关键解决步骤

  • 定位要替换的@片段:先找到光标位置前最近的@\w+内容(也就是你输入的@te),确定它在文本中的起止位置。
  • 删除原有@文本:通过Range对象精准选中这段文本并删除,再插入新的带样式的span标签。
  • 重置光标位置:插入标签后把光标移到标签后面,保证后续输入可以正常进行。

修改后的完整代码

HTML

<div contenteditable="true" id="div_id"> 
  First person <span class="tagged-user" id="1">@Joe </span> and @te and <span class="tagged-user" id="2">@Emilie</span> want to go to the beach. 
</div>

CSS

.tagged-user { 
  color: #1e81d6; 
  font-weight: bold; 
}

JavaScript

var input = document.getElementById("div_id"); 
var username = "TEST"; 
input.focus(); 

var selection = window.getSelection(); 
var range = selection.getRangeAt(0);

// 关键:获取光标前的文本并找到要替换的@片段
var beforeCaretText = getTextBeforeCaret(input);
// 匹配最后一个@开头的单词(非贪婪匹配,确保只取最近的那个)
var match = beforeCaretText.match(/@\w+$/);
if (match) {
  var textToReplace = match[0];
  // 计算这段文本在输入框中的起始位置
  var startPos = beforeCaretText.length - textToReplace.length;
  
  // 创建新的Range来选中要替换的文本
  var replaceRange = document.createRange();
  replaceRange.setStart(input.firstChild, startPos);
  replaceRange.setEnd(range.startContainer, range.startOffset);
  
  // 删除原有@文本
  replaceRange.deleteContents();
  
  // 插入新的标签
  var span = document.createElement('span'); 
  span.setAttribute('class', 'tagged-user'); 
  span.setAttribute('id', 55); 
  span.textContent = username; // 用textContent避免HTML注入问题
  
  replaceRange.insertNode(span);
  // 插入空格并把光标移到空格后面
  replaceRange.collapse(false);
  var spaceNode = document.createTextNode("\u00a0");
  replaceRange.insertNode(spaceNode);
  replaceRange.collapse(false);
  
  // 更新选区
  selection.removeAllRanges(); 
  selection.addRange(replaceRange);
}

// 辅助函数:获取光标前的所有文本
function getTextBeforeCaret(element) {
  var caretOffset = 0;
  var doc = element.ownerDocument || element.document;
  var win = doc.defaultView || doc.parentWindow;
  var sel;
  if (typeof win.getSelection != "undefined") {
    sel = win.getSelection();
    if (sel.rangeCount > 0) {
      var range = win.getSelection().getRangeAt(0);
      var preCaretRange = range.cloneRange();
      preCaretRange.selectNodeContents(element);
      preCaretRange.setEnd(range.endContainer, range.endOffset);
      caretOffset = preCaretRange.toString().length;
    }
  } else if ( (sel = doc.selection) && sel.type != "Control") {
    var textRange = sel.createRange();
    var preCaretTextRange = doc.body.createTextRange();
    preCaretTextRange.moveToElementText(element);
    preCaretTextRange.setEndPoint("EndToEnd", textRange);
    caretOffset = preCaretTextRange.text.length;
  }
  return element.textContent.substring(0, caretOffset);
}

代码说明

  1. getTextBeforeCaret辅助函数:用来获取光标之前的所有文本,确保我们能准确找到要替换的@te。
  2. 匹配与删除:通过正则/@\w+$/找到光标前最后一个@开头的单词,然后创建Range选中这段文本并删除。
  3. 插入与光标调整:插入带样式的span标签后,添加一个空格并把光标移到空格后面,保证用户可以继续输入。

测试你提到的场景:hello man, @te @emilie how are you?,当光标在@te后面时,执行这段代码会把@te替换成<span class="tagged-user" id="55">TEST</span> ,不会出现重复的情况。

内容的提问来源于stack exchange,提问作者oalkaako

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:08:04