contenteditable div光标位置插入span包裹的自动补全内容异常求助
修复contenteditable自动补全插入位置错乱问题
问题描述
现有代码实现了点击「Add」按钮添加可编辑div,输入「<>」时展示其他同类型div内容的建议列表,但点击下拉选项时,无法将选中内容(需用span包裹以便后续着色)插入到正确光标位置,插入位置出现错乱。
修复后的完整代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Textarea Suggestions</title> <link rel="stylesheet" href="style.css"> <style> /* 可选:测试span着色效果 */ .suggestion-span { color: blue; font-weight: bold; } </style> </head> <body> <div class="container"> <button id="addButton">Add</button> <div class="textarea-container"> </div> </div> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script> let count = 0; let firstBackspacePressed = false; let suggestion_selected = ""; let cposition = 0; // 保存当前活跃编辑框和光标位置 let activeEditor = null; let lastCaretPosition = 0; $(document).ready(function () { let textareaCount = 0; const addButton = $("#addButton"); addButton.click(function () { textareaCount++; const textareaId = textareaCount; const textareaHtml = `<div class="textarea-item"><div class="edit" role="textbox" contenteditable="true" id="${textareaId}"></div><ul class="autocomplete-list"></ul></div>`; $(".textarea-container").append(textareaHtml); }); let textareaValues = []; $(".textarea-container").on("input", ".edit", function (e) { const div = $(this); activeEditor = div[0]; const selection = window.getSelection(); if (selection.rangeCount > 0) { lastCaretPosition = selection.getRangeAt(0).startOffset; } const newValue = div.text(); if(e.keyCode === 8){ const updatedValue = newValue.substring(newValue.indexOf("<>"), newValue.lastIndexOf("'<>")); return div.textContent = updatedValue; } const suggestionsList = div.next(".autocomplete-list"); const value = div.text(); const lastBracketIndex = value.lastIndexOf("<>", lastCaretPosition); count = lastBracketIndex; const index = $(".textarea-container .edit").index(div); textareaValues[index] = value; if (lastBracketIndex !== -1) { const filterText = value.substring(lastBracketIndex + 2, lastCaretPosition); const suggestionArr = textareaValues .filter((item, idx) => idx !== index && item.includes(filterText)) .map(item => item); const suggestionItems = suggestionArr.map(suggestion => `<li>${suggestion}</li>`).join(""); suggestionsList.html(suggestionItems); suggestionsList.show(); } else { suggestionsList.empty(); suggestionsList.hide(); } }); // 修复后的建议点击事件 $(".textarea-container").on("click", ".autocomplete-list li", function () { const selectedSuggestion = $(this).text(); if (!activeEditor) return; // 创建带span包裹的内容 const span = document.createElement("span"); span.className = "suggestion-span"; span.textContent = selectedSuggestion; // 恢复光标位置并插入内容 const selection = window.getSelection(); selection.removeAllRanges(); const range = document.createRange(); range.setStart(activeEditor.firstChild || activeEditor, lastCaretPosition); range.collapse(true); range.insertNode(span); // 将光标移到插入内容后方 range.setStartAfter(span); selection.addRange(range); // 更新内容记录 const index = $(".textarea-container .edit").index($(activeEditor)); textareaValues[index] = $(activeEditor).text(); // 隐藏建议列表 $(this).closest(".autocomplete-list").empty().hide(); }); $(".textarea-container").on("keydown", ".edit", function (e) { const div = $(this); let id = $(this).attr('id'); const value = div.text(); const selection = window.getSelection(); const caretPosition = selection.rangeCount > 0 ? selection.getRangeAt(0).startOffset : 0; const lastBracketIndex = value.lastIndexOf("<>", caretPosition); var pos = (0 + caretPosition - (suggestion_selected.length + 2)); const matchingSubstring = textareaValues.slice(0,-1) .find(item => item.includes("<>"+value) ); if(value.includes('<>')){ if (e.key === 'Backspace' || e.key === 'Delete') { if (firstBackspacePressed) { firstBackspacePressed = false; $(this).unbind('keydown'); } else { e.preventDefault(); if(value.substring(lastBracketIndex, caretPosition).indexOf(" ") === -1){ // selectText(lastBracketIndex, caretPosition, id); } firstBackspacePressed = true; } } } }); }); function selectText(start, end, id) { const range = document.createRange(); range.setStart(document.getElementById(id).firstChild, start); range.setEnd(document.getElementById(id).firstChild, end); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); } function deleteSelectedText() { const selection = window.getSelection(); if (!selection.isCollapsed) { selection.deleteFromDocument(); } } function insertTextAtCursor(text) { var sel, range; sel = window.getSelection(); if (sel.rangeCount > 0) { range = sel.getRangeAt(0); range.deleteContents(); var textNode = document.createTextNode(text); range.insertNode(textNode); range.setStartAfter(textNode); sel.removeAllRanges(); sel.addRange(range); } } function keyHandle(evt) { key = evt.keyCode; switch(key) { case 9: //Tab insertTextAtCursor('\t'); evt.preventDefault(); break case 13: //Enter insertTextAtCursor('\n'); evt.preventDefault(); break } } </script> </body> </html>
关键改动说明
- 保存编辑上下文:在
input事件中记录当前活跃的编辑框和光标位置,避免点击建议时选区丢失导致位置错误 - DOM API插入内容:直接创建span元素并通过Range API插入到正确位置,替代字符串拼接方式,确保插入位置准确且内容被span包裹
- 优化光标定位:插入内容后自动将光标移到span后方,保证编辑体验连贯
- 移除重复依赖:删除重复引入的jQuery版本,避免冲突
内容的提问来源于stack exchange,提问作者Anirudh S
相关产品推荐
相关产品推荐

