如何在contenteditable的div中将单词包裹在span内并保留光标位置?
问题
如何在设置了contenteditable属性的<div>中,将每个单词包裹在<span>元素内,同时保留光标位置?
示例效果:用户输入the fox guy jumped over the dog时,最终DOM结构应为:
<span>the</span> <span>fox</span> <span>guy</span> <span>jumped</span> <span>over</span> <span>the</span> <span>dog</span>
已尝试方案
现有代码如下,问题是修改innerHTML后光标会直接跳转到内容末尾:
document.querySelector("#container").addEventListener("input", function() { this.innerHTML = "<span>" + this.innerText .replaceAll("&", "&") // 转义HTML特殊字符 .replaceAll("<", "&lt;") .replaceAll(">", "&gt;") .split(" ") .join("</span><span>") + "</span>"; });
补充说明
需要使用input事件实现功能,要在用户输入过程中实时拆分单词,不能等编辑区域失焦才触发。
解决方案
核心思路是:修改DOM前先记录光标相对于纯文本内容的偏移量,DOM更新完成后再把光标还原到对应的偏移位置即可,完整实现代码如下:
const editableDiv = document.querySelector("#container"); // 工具函数:获取光标相对于可编辑元素纯文本的偏移量 function getTextCaretOffset(el) { const selection = window.getSelection(); let offset = 0; if (selection.rangeCount > 0) { const range = selection.getRangeAt(0); const preCaretRange = range.cloneRange(); preCaretRange.selectNodeContents(el); preCaretRange.setEnd(range.endContainer, range.endOffset); offset = preCaretRange.toString().length; } return offset; } // 工具函数:根据纯文本偏移量还原光标位置 function setTextCaretOffset(el, targetOffset) { const selection = window.getSelection(); const range = document.createRange(); range.setStart(el, 0); let currentOffset = 0; const nodeStack = [el]; let node, found = false; while (node = nodeStack.pop()) { if (node.nodeType === Node.TEXT_NODE) { const nodeTextLength = node.length; // 判断目标偏移是否在当前文本节点范围内 if (targetOffset >= currentOffset && targetOffset <= currentOffset + nodeTextLength) { range.setStart(node, targetOffset - currentOffset); range.collapse(true); found = true; break; } currentOffset += nodeTextLength; } else { // 倒序压入子节点,遍历时顺序和DOM顺序一致 let i = node.childNodes.length; while (i--) { nodeStack.push(node.childNodes[i]); } } } if (found) { selection.removeAllRanges(); selection.addRange(range); } } // 输入事件监听 editableDiv.addEventListener("input", function() { // 1. 先记录修改前的光标偏移 const caretOffset = getTextCaretOffset(this); // 2. 执行单词包裹逻辑,注意这里加了空格,避免单词间空格丢失 this.innerHTML = "<span>" + this.innerText .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .split(" ") .join("</span> <span>") + "</span>"; // 3. 恢复光标到原位置 setTextCaretOffset(this, caretOffset); });
说明
- 代码针对原有逻辑做了小调整,拼接
span时加入了空格,避免单词之间的空格被吞掉 - 偏移量计算完全基于纯文本长度,不受包裹的
span标签影响,光标定位准确 - 全程基于
input事件触发,满足实时处理的需求
内容的提问来源于stack exchange,提问作者illogicalapple
相关产品推荐
相关产品推荐

