实现鼠标选中整词添加mark标签并替换原有标记的方案求助
解决方案
核心思路
- 清除原有标记:每次选中新文本前,先移除所有
<mark>标签,将其内容还原为普通文本。 - 限制完整单词选择:通过调整选区范围,确保选中的是完整的单词(避免选中单词的一部分)。
- 处理跨节点选区:兼容包含
<br>等非文本节点的选区,只针对文本内容进行操作。
完整实现代码
<div id="editordiv" contenteditable>Some multiline text here<br>lines</div>
const editordiv = document.getElementById("editordiv"); document.addEventListener('selectionchange', handleSelectionChange); function handleSelectionChange() { // 仅处理编辑区的选区变化 if (document.activeElement !== editordiv) return; const selection = window.getSelection(); // 无选中内容时清空标记并返回 if (selection.isCollapsed) { clearExistingMarks(); return; } const range = selection.getRangeAt(0); // 调整选区至完整单词范围 adjustRangeToWholeWord(range); // 清除之前的标记 clearExistingMarks(); // 为选中的完整单词添加mark标签 if (!range.collapsed) { const mark = document.createElement('mark'); range.surroundContents(mark); // 重新选中标记后的内容,避免选区丢失 const newRange = document.createRange(); newRange.selectNodeContents(mark); selection.removeAllRanges(); selection.addRange(newRange); } } // 移除所有已存在的mark标签,恢复原文本 function clearExistingMarks() { const marks = editordiv.querySelectorAll('mark'); marks.forEach(mark => { const parent = mark.parentNode; // 将mark内部的内容移到父节点中,保留原有结构 while (mark.firstChild) { parent.insertBefore(mark.firstChild, mark); } parent.removeChild(mark); }); } // 调整选区范围,确保选中完整单词 function adjustRangeToWholeWord(range) { let startNode = range.startContainer; let startOffset = range.startOffset; let endNode = range.endContainer; let endOffset = range.endOffset; // 处理选区起始位置:向左扩展到单词开头 if (startNode.nodeType === Node.TEXT_NODE) { const text = startNode.textContent; // 跳过起始位置左侧的单词字符,找到单词开头 while (startOffset > 0 && /\w/.test(text[startOffset - 1])) { startOffset--; } // 跳过起始位置的非单词字符(比如空格、标点) while (startOffset < text.length && !/\w/.test(text[startOffset])) { startOffset++; } range.setStart(startNode, startOffset); } // 处理选区结束位置:向右扩展到单词结尾 if (endNode.nodeType === Node.TEXT_NODE) { const text = endNode.textContent; // 跳过结束位置右侧的单词字符,找到单词结尾 while (endOffset < text.length && /\w/.test(text[endOffset])) { endOffset++; } // 跳过结束位置的非单词字符 while (endOffset > 0 && !/\w/.test(text[endOffset - 1])) { endOffset--; } range.setEnd(endNode, endOffset); } // 如果调整后选区折叠(比如选中的是空格、换行这类非单词内容),则清空选区 if (range.collapsed) { window.getSelection().removeAllRanges(); } }
代码说明
clearExistingMarks:遍历所有<mark>标签,将标签内的文本节点移到父节点后删除标签,实现标记清除和文本还原,不破坏原有DOM结构。adjustRangeToWholeWord:通过正则匹配单词字符(\w匹配字母、数字、下划线),调整选区的起始/结束位置,确保选中的是完整单词;若选中的是非单词内容,则直接清空选区。handleSelectionChange:监听全局选区变化,仅处理编辑区内的操作,先调整选区范围,再清除旧标记,最后给新选中的完整单词添加<mark>标签并重新选中内容。
该方案可兼容包含<br>等非文本节点的场景,仅对文本节点进行处理,避免非文本标签干扰选区逻辑。
内容的提问来源于stack exchange,提问作者Bobberson3108
相关产品推荐
相关产品推荐

