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

React中如何基于自定义词表为textarea实现拼写检查并添加红色波浪线

React中基于自定义词表的拼写检查实现(带红色波浪线)

原生<textarea>依赖系统词典,无法满足自定义词表的拼写检查需求,且不能单独为错误单词添加样式。下面是基于contenteditable模拟textarea的实现方案,支持自定义词表检查和红色波浪线标记。

核心实现思路

用contenteditable的<div>替代原生textarea,原因是它允许给单个文本片段添加样式;配合自定义拼写检查逻辑,将不在词表中的单词用带波浪线样式的<span>包裹;同时处理光标位置,避免重新渲染后光标丢失。

完整代码实现

组件代码

import { useState, useRef, useEffect } from 'react';

const CustomSpellCheckEditor = () => {
  // 自定义词表:可根据需求动态更新(比如从后端加载)
  const customDictionary = new Set(['react', '拼写检查', '波浪线', '组件', 'javascript', 'frontend']);
  const [content, setContent] = useState('');
  const editorRef = useRef(null);

  // 保存当前光标位置
  const saveCursorPos = () => {
    const selection = window.getSelection();
    if (!selection.rangeCount) return 0;
    const range = selection.getRangeAt(0);
    const preRange = range.cloneRange();
    preRange.selectNodeContents(editorRef.current);
    preRange.setEnd(range.startContainer, range.startOffset);
    return preRange.toString().length;
  };

  // 恢复光标位置
  const restoreCursorPos = (pos) => {
    const selection = window.getSelection();
    const range = document.createRange();
    range.setStart(editorRef.current, 0);
    range.collapse(true);
    const textNode = editorRef.current.firstChild;
    if (textNode) {
      range.setEnd(textNode, Math.min(pos, textNode.length));
    }
    selection.removeAllRanges();
    selection.addRange(range);
  };

  // 拼写检查并格式化内容
  const formatContent = (text) => {
    if (!text) return '';
    // 拆分文本为「中文词/英文词/标点符号」,可根据需求优化正则
    const splitRegex = /([\u4e00-\u9fa5]+|[a-zA-Z]+|[^\w\s])/g;
    const parts = text.match(splitRegex) || [];
    
    return parts.map(part => {
      // 判断是否为需要检查的单词(过滤标点)
      const isCheckable = /[\u4e00-\u9fa5]|[a-zA-Z]/.test(part);
      if (isCheckable && !customDictionary.has(part.toLowerCase())) {
        return `<span class="spell-error">${part}</span>`;
      }
      return part;
    }).join('');
  };

  // 处理输入事件
  const handleInput = (e) => {
    const currentText = e.target.innerText;
    const cursorPos = saveCursorPos();
    setContent(currentText);
    // 异步恢复光标,确保DOM更新完成
    setTimeout(() => restoreCursorPos(cursorPos), 0);
  };

  // 内容更新时重新渲染格式化后的HTML
  useEffect(() => {
    if (editorRef.current) {
      editorRef.current.innerHTML = formatContent(content);
    }
  }, [content]);

  return (
    <div
      ref={editorRef}
      contentEditable
      onInput={handleInput}
      className="custom-editor"
      spellCheck="false" // 禁用原生拼写检查
      placeholder="请输入内容..."
    />
  );
};

export default CustomSpellCheckEditor;

样式代码

.custom-editor {
  width: 100%;
  min-height: 160px;
  padding: 10px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  font-family: system-ui, -apple-system, sans-serif;
  font-size: 16px;
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  outline: none;
  resize: vertical;
  background-color: white;
}

.spell-error {
  text-decoration: underline wavy #ef4444;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

关键细节说明

  • 词表效率:用Set存储词表,查询时间复杂度为O(1),比数组更高效。
  • 文本拆分:示例正则适配中英文混合场景,可根据需求扩展(比如处理连字符、缩写等)。
  • 光标处理:通过Selection和RangeAPI保存/恢复光标,解决重新渲染后光标丢失的问题。
  • 样式模拟:通过white-space: pre-wrap、resize等属性让编辑器外观和行为接近原生textarea。

可选优化方向

  • 动态更新词表:添加输入框让用户自定义添加新词,直接更新customDictionary即可生效。
  • 局部检查优化:长文本场景下,仅检查光标附近的单词,减少计算量。
  • 错误单词右键菜单:添加右键选项(比如添加到词表、忽略),提升交互体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 04:48:16