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

React带行号纯文本编辑器行高匹配问题求助

解决方案:基于镜像容器的行号高度匹配

针对你遇到的「仅为真实换行显示行号,容器自动换行不显示」的问题,你的镜像容器思路是可行的,核心问题出在镜像样式同步和DOM渲染时机的获取上。以下是具体实现方案:

关键思路

  1. 让隐藏的镜像容器完全复制编辑器的样式(字体、字号、行高、宽度、内边距等),确保自动换行行为和编辑器一致;
  2. 将真实换行(\n)分割的内容,逐个放入镜像容器的div中;
  3. 在DOM渲染完成后,获取镜像每行的高度,同步到对应行号的div上。

完整代码实现

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

const LineNumberEditor = () => {
  const editorRef = useRef(null);
  const mirrorContainerRef = useRef(null);
  const lineNumbersRef = useRef(null);
  const [content, setContent] = useState('');

  // 同步编辑器内容到状态(驱动镜像更新)
  useEffect(() => {
    const syncContent = () => {
      if (!editorRef.current) return;
      setContent(editorRef.current.innerText);
    };

    const editor = editorRef.current;
    editor?.addEventListener('input', syncContent);
    // 初始化同步一次
    syncContent();
    return () => editor?.removeEventListener('input', syncContent);
  }, []);

  // 同步行号高度到镜像行高度
  useEffect(() => {
    if (!mirrorContainerRef.current || !lineNumbersRef.current) return;

    // 等待DOM渲染完成后再获取高度
    requestAnimationFrame(() => {
      const mirrorLines = mirrorContainerRef.current.querySelectorAll('.mirror-line');
      const numberLines = lineNumbersRef.current.querySelectorAll('.line-number');

      mirrorLines.forEach((mirrorLine, idx) => {
        if (numberLines[idx]) {
          numberLines[idx].style.height = `${mirrorLine.offsetHeight}px`;
        }
      });
    });
  }, [content]);

  // 监听窗口 resize,同步镜像容器宽度
  useEffect(() => {
    const handleResize = () => {
      if (editorRef.current && mirrorContainerRef.current) {
        mirrorContainerRef.current.style.width = `${editorRef.current.offsetWidth}px`;
        // 触发高度重新计算
        setContent(editorRef.current.innerText);
      }
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  const lines = content.split('\n');

  return (
    <div style={{ display: 'flex', gap: '10px', padding: '10px', position: 'relative' }}>
      {/* 行号区域 */}
      <div 
        ref={lineNumbersRef}
        style={{ 
          textAlign: 'right', 
          paddingRight: '8px', 
          userSelect: 'none', 
          color: '#666',
          fontFamily: 'monospace',
          fontSize: '14px',
          lineHeight: '1.5',
          paddingTop: '8px'
        }}
      >
        {lines.map((_, idx) => (
          <div key={idx} className="line-number">
            {idx + 1}
          </div>
        ))}
      </div>

      {/* 编辑器区域 */}
      <div
        ref={editorRef}
        contentEditable
        suppressContentEditableWarning={true}
        style={{
          flex: 1,
          minHeight: '250px',
          padding: '8px',
          border: '1px solid #e0e0e0',
          borderRadius: '4px',
          fontFamily: 'monospace',
          fontSize: '14px',
          lineHeight: '1.5',
          whiteSpace: 'pre-wrap'
        }}
      />

      {/* 隐藏的镜像容器 */}
      <div
        ref={mirrorContainerRef}
        style={{
          position: 'absolute',
          top: 0,
          left: '-9999px',
          visibility: 'hidden',
          pointerEvents: 'none',
          fontFamily: 'monospace',
          fontSize: '14px',
          lineHeight: '1.5',
          padding: '8px',
          whiteSpace: 'pre-wrap',
          width: editorRef.current?.offsetWidth || '500px'
        }}
      >
        {lines.map((line, idx) => (
          <div key={idx} className="mirror-line">
            {line || '\u00A0'} {/* 空行用非换行空格填充,避免高度为0 */}
          </div>
        ))}
      </div>
    </div>
  );
};

export default LineNumberEditor;

核心细节说明

  • 样式完全同步:镜像容器的字体、字号、行高、宽度、内边距必须和编辑器完全一致,否则自动换行的行数会不匹配;
  • 空行处理:用\u00A0(非换行空格)填充空行的镜像div,避免因为无内容导致高度为0,进而行号无法对齐;
  • DOM渲染时机:用requestAnimationFrame确保在镜像容器渲染完成后再获取高度,解决useEffect中直接获取不到最新DOM高度的问题;
  • 动态宽度适配:监听窗口resize事件,同步镜像容器的宽度,保证窗口变化时自动换行行为和编辑器一致。

这个方案既保留了你已实现的「避免重渲染丢失光标」的特性,又完美解决了仅为真实换行显示行号的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 14:03:26