聊天应用@提及用户后光标位置丢失问题及解决方案咨询
问题:contenteditable文本框插入@提及后光标丢失
开发聊天应用时,实现了在contenteditable输入框输入@后加载匹配用户列表的功能,但点击列表项替换文本后,光标位置丢失。
解决思路
光标丢失的核心原因是替换innerHTML后,原有的选区(光标)被重置。需要在替换文本前记录光标相关的位置,替换后重新定位光标到提及内容的末尾。
具体处理步骤:
- 替换文本前,计算好替换后光标应该停留的位置(新增提及内容的末尾+空格位置)
- 替换完成后,使用
Range和SelectionAPI手动恢复光标位置 - 处理contenteditable中的HTML元素结构,避免因元素嵌套导致定位错误
修改后的JavaScript代码
let adUsername = null; $(document).off('keyup', '#w-input-text'); $(document).on('keyup', '#w-input-text', function(event) { if ($.inArray(event.keyCode, [9, 16, 17, 18]) !== -1) return; let inputValue = $(this).text(); if (inputValue.length == 0) return; inputValue = getActiveWord(); if (!inputValue.includes("@")) return; inputValue = inputValue.replace('@', ''); if (inputValue.length > 0) { if (adUsername == inputValue) return; adUsername = inputValue; $.ajax({ url: BASE_URL + 'user-list/' + adUsername, method: 'GET', dataType: 'json', success: function(response) { // Clear the user list $('#userListOnAd').empty(); // Populate the user list with the retrieved data for (let i = 0; i < response.data.length; i++) { let name = response.data[i].name; let user_id = response.data[i].user_id; let listItem = $('<li>').text(name); listItem.on('click', function() { let selectedUsername = $(this).text(); $('#userListOnAd').hide(); // 替换文本并恢复光标 replaceAndRestoreCursor(selectedUsername); }); $('#userListOnAd').append(listItem); } // Show the user list $('#userListOnAd').show(); }, error: function() { // Handle the error case } }); } else { // Hide the user list if the input does not match the pattern $('#userListOnAd').hide(); } }); function getActiveWord() { let text = $('#w-input-text').text(); let sel = window.getSelection(); let range = sel.getRangeAt(0); let start_index = range.startOffset; let end_index = range.endOffset; let previous_space_index = text.lastIndexOf(" ", start_index - 1); let next_space_index = text.indexOf(" ", end_index); let begin = previous_space_index < 0 ? 0 : previous_space_index + 1; let end = next_space_index < 0 ? text.length : next_space_index; let between_spaces = text.substring(begin, end); return between_spaces; } // 替换文本并恢复光标 function replaceAndRestoreCursor(selectedUsername) { const inputEl = $('#w-input-text')[0]; let text = inputEl.textContent; let sel = window.getSelection(); let range = sel.getRangeAt(0); let start_index = range.startOffset; let end_index = range.endOffset; let previous_space_index = text.lastIndexOf(" ", start_index - 1); let next_space_index = text.indexOf(" ", end_index); let begin = previous_space_index < 0 ? 0 : previous_space_index + 1; let end = next_space_index < 0 ? text.length : next_space_index; let wordToReplace = text.substring(begin, end); // 计算替换后光标目标位置 const replacementContent = `<mention data-id="101">@${selectedUsername}</mention> `; const cursorPosition = begin + replacementContent.length; // 构造新HTML内容 let substringBefore = text.substring(0, begin); let substringAfter = text.substring(end); let updatedHtml = substringBefore + replacementContent + substringAfter; // 更新输入框内容 inputEl.innerHTML = updatedHtml; // 恢复光标位置 setCursorPosition(inputEl, cursorPosition); } // 工具函数:设置contenteditable的光标位置 function setCursorPosition(element, position) { const selection = window.getSelection(); const range = document.createRange(); let currentNode = element.firstChild; let currentLength = 0; // 遍历节点找到对应位置 while (currentNode) { if (currentNode.nodeType === Node.TEXT_NODE) { if (currentLength + currentNode.length >= position) { range.setStart(currentNode, position - currentLength); range.setEnd(currentNode, position - currentLength); break; } else { currentLength += currentNode.length; } } currentNode = currentNode.nextSibling; } // 未找到对应文本节点时,光标定位到末尾 if (!currentNode) { range.selectNodeContents(element); range.collapse(false); } selection.removeAllRanges(); selection.addRange(range); }
HTML代码
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <ul id="userListOnAd"> <li>Durvash Nimje</li> <li>ABC Nimje</li> <li>XYZ Nimje</li> <li>PQR Nimje</li> </ul> <div id="w-input-text" contenteditable></div>
关键修改点
- 新增
replaceAndRestoreCursor函数,整合文本替换与光标恢复逻辑 - 添加
setCursorPosition工具函数,精确计算并定位光标位置 - 替换文本前预先计算光标目标位置,避免替换后选区丢失
内容的提问来源于stack exchange,提问作者Durvash Nimje
相关产品推荐
相关产品推荐

