如何在HTML可编辑区域(textarea/contenteditable div)高亮指定词汇?
实现可编辑区域的自定义词汇高亮方案
我之前也碰到过类似的需求,那些语法高亮库确实不太适合这种自定义非代码词汇的场景,自己写一套其实没那么复杂,给你分享一个亲测好用的方案:
核心思路
用contenteditable="true"的div代替textarea(因为textarea没法内嵌HTML标签来实现高亮),监听输入事件,实时扫描文本中的目标词汇,用带样式的span包裹,同时处理好光标位置(这是关键,不然用户输入时光标乱跑体验极差)。
完整代码示例
HTML
<div class="highlight-editable" contenteditable="true" placeholder="输入内容试试,Hi、foo会被高亮"></div>
CSS
.highlight-editable { width: 100%; min-height: 150px; border: 1px solid #ccc; padding: 10px; border-radius: 4px; font-size: 16px; } .highlight { background-color: #ffeb3b; padding: 2px 4px; border-radius: 2px; } /* 处理placeholder样式 */ .highlight-editable:empty:before { content: attr(placeholder); color: #999; }
JavaScript
// 定义需要高亮的目标词汇 const targetWords = ['Hi', 'foo']; // 生成匹配正则(这里用了i修饰符,不区分大小写,需要区分的话去掉i) const highlightRegex = new RegExp(`\\b(${targetWords.join('|')})\\b`, 'gi'); const editableDiv = document.querySelector('.highlight-editable'); let debounceTimer = null; // 保存当前光标位置的函数 function saveCursorPosition() { const selection = window.getSelection(); if (selection.rangeCount === 0) return null; const range = selection.getRangeAt(0); const preSelectionRange = range.cloneRange(); preSelectionRange.selectNodeContents(editableDiv); preSelectionRange.setEnd(range.startContainer, range.startOffset); return preSelectionRange.toString().length; } // 恢复光标位置的函数 function restoreCursorPosition(pos) { const selection = window.getSelection(); const range = document.createRange(); range.setStart(editableDiv, 0); range.collapse(true); const textNode = editableDiv.firstChild; const endPos = textNode ? Math.min(pos, textNode.length) : 0; range.setEnd(textNode, endPos); selection.removeAllRanges(); selection.addRange(range); } // 核心高亮处理函数 function highlightText() { // 先保存光标位置 const cursorPos = saveCursorPosition(); // 先清除已有的高亮,避免重复包裹span const plainText = editableDiv.textContent; // 替换目标词汇为高亮span const highlightedText = plainText.replace(highlightRegex, '<span class="highlight">$1</span>'); // 更新div内容 editableDiv.innerHTML = highlightedText; // 恢复光标位置 if (cursorPos !== null) { setTimeout(() => restoreCursorPosition(cursorPos), 0); } } // 监听输入事件,加防抖避免频繁触发 editableDiv.addEventListener('input', () => { clearTimeout(debounceTimer); debounceTimer = setTimeout(highlightText, 100); }); // 初始化时触发一次(如果有默认内容的话) highlightText();
关键细节说明
为什么用contenteditable div?
textarea只能处理纯文本,无法插入HTML标签来实现高亮样式,而contenteditable div支持内嵌HTML,完美适配需求。光标位置处理
直接替换innerHTML会导致光标跳到内容末尾,所以我们先保存光标相对于文本开头的偏移量,替换完成后再恢复这个位置,保证输入体验流畅。删除时的处理
每次输入(包括删除)都会触发重新扫描:先把当前内容转换成纯文本(清除所有旧的span),再重新匹配高亮词汇生成新的HTML。这样即使用户删除了高亮词汇的一部分,旧的span也会被清理,不会残留无效标签。防抖优化
输入事件会频繁触发,加个100ms的防抖可以避免频繁执行高亮逻辑,提升性能。
可选优化
- 如果需要支持动态添加/删除目标词汇,只需要更新
targetWords数组,然后重新生成highlightRegex即可。 - 可以调整正则表达式,比如去掉
\b(单词边界)来匹配包含目标词汇的字符串,比如匹配“HiThere”里的“Hi”。 - 如果需要更精确的光标恢复(比如光标在高亮span中间的情况),可以优化
saveCursorPosition和restoreCursorPosition函数,考虑HTML节点的层级结构。
内容的提问来源于stack exchange,提问作者pgr4567
相关产品推荐
相关产品推荐

