如何在含多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); }
代码说明
getTextBeforeCaret辅助函数:用来获取光标之前的所有文本,确保我们能准确找到要替换的@te。- 匹配与删除:通过正则
/@\w+$/找到光标前最后一个@开头的单词,然后创建Range选中这段文本并删除。 - 插入与光标调整:插入带样式的span标签后,添加一个空格并把光标移到空格后面,保证用户可以继续输入。
测试你提到的场景:hello man, @te @emilie how are you?,当光标在@te后面时,执行这段代码会把@te替换成<span class="tagged-user" id="55">TEST</span> ,不会出现重复的情况。
内容的提问来源于stack exchange,提问作者oalkaako
相关产品推荐
相关产品推荐

