You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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();

关键细节说明

  1. 为什么用contenteditable div?
    textarea只能处理纯文本,无法插入HTML标签来实现高亮样式,而contenteditable div支持内嵌HTML,完美适配需求。

  2. 光标位置处理
    直接替换innerHTML会导致光标跳到内容末尾,所以我们先保存光标相对于文本开头的偏移量,替换完成后再恢复这个位置,保证输入体验流畅。

  3. 删除时的处理
    每次输入(包括删除)都会触发重新扫描:先把当前内容转换成纯文本(清除所有旧的span),再重新匹配高亮词汇生成新的HTML。这样即使用户删除了高亮词汇的一部分,旧的span也会被清理,不会残留无效标签。

  4. 防抖优化
    输入事件会频繁触发,加个100ms的防抖可以避免频繁执行高亮逻辑,提升性能。

可选优化

  • 如果需要支持动态添加/删除目标词汇,只需要更新targetWords数组,然后重新生成highlightRegex即可。
  • 可以调整正则表达式,比如去掉\b(单词边界)来匹配包含目标词汇的字符串,比如匹配“HiThere”里的“Hi”。
  • 如果需要更精确的光标恢复(比如光标在高亮span中间的情况),可以优化saveCursorPosition和restoreCursorPosition函数,考虑HTML节点的层级结构。

内容的提问来源于stack exchange,提问作者pgr4567

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:12:43