根据渲染时间信息(即行高)调整 React DOM 中的元素

Adjusting elements in the React DOM based on render-time information (i.e. line height)

提问人:Micrified 提问时间:11/8/2023 更新时间:11/8/2023 访问量:37

问:

上下文

我使用 React JS 创建了一个简单的带有行号的文本编辑器。它应该只编辑纯文本(即不需要富文本编辑)。text-input 字段用 div 表示。打字时,我面临三个挑战:contentEditable

  1. 我希望每个换行符都显示行号。
  2. 我不想重新渲染到用户正在输入的 DOM,因为它会重置光标(请参阅此处的类似问题)。
  3. 我希望行号只出现在实际的换行符上。不适用于由于包含框的边界而环绕的线。

尝试的解决方案

  1. 为了显示行号,我在可编辑内容旁边创建了一列数字。我使用 React 为每行创建一个。✅map
  2. 为了避免 DOM 重新渲染的问题,我曾经确保我的文本输入不会被重新渲染(并且光标位置不会丢失)。✅React.useRef
  3. 为了确保行号只出现在实际换行符旁边,我尝试将文本输入镜像到隐藏的 div 中。这个想法是我可以将每一行插入到它自己的 div 中。然后,我可以获取每行 div 的高度,并使用它来设置行号 div 的高度,以便它们匹配(见下图): ❌

enter image description here

不幸的是,这目前并没有完全按照我想要的方式工作。我似乎无法弄清楚如何获取渲染的 div 的高度,然后将它们设置为行号。我试图用它来在 DOM 渲染运行,但我无法找到如何“钩住”到 div-array 中并计算它们的高度。useEffect


最小代码示例

请参阅此工作 JSFiddle 示例。不幸的是,我无法让它在 StackOverflow 代码工具中工作。为方便起见,我还是粘贴了下面的相关代码:

React + JavaScript的

const initialText=`Nature's first green is gold,
Her hardest hue to hold.
Her early leaf's a flower;
But only so an hour.
Then leaf subsides to leaf.
So Eden sank to grief,
So dawn does down to day.
Nothing gold can stay.`

function App () {
  const [lines, setLines] = React.useState(initialText.split("\n"));
  let text = React.useRef(initialText)
  
  React.useEffect(() => { 
    console.log('Rendered!')
    // TODO: Update the height of each line-number to match the real-lines
  });
  
  function handleInput(e) {
    console.log('Edited!')
    setLines(e.target.textContent.split("\n"));
  }
  
  return (
    <div  className='container'>
      <div className='margin'>
          {lines.map((line,i)=>(<div>{(1+i).toString()}</div>))}
      </div>

      <div className='editor-container'>
        <div onInput={handleInput} contentEditable='plaintext-only' className='editor' suppressContentEditableWarning={true}>
          {text.current}
        </div>
        <div className='editor' style={{visibility:'hidden'}}>
          {lines.map(line=>(<div>{line}</div>))}
        </div>
      </div>
    </div>
  )
}
const root = ReactDOM.createRoot(document.getElementById("app"))
root.render(<App/>)

CSS的

.container {
  display: flex;
  justify-content: space-between;
}

.margin {
  text-align: right;
  flex-shrink: 0;
  max-width: 1em;
}

.margin > div {
  width: 100%;
}

.editor-container {
  margin-left: 0.5em;
  flex-grow: 1;
  position: relative;
}

.editor {
  flex-grow: 1;
  position: absolute;
  white-space: pre-wrap;
  width: 100%;
  height: 100%;
}

[HTML全

<div id="app"></div>

JSFiddle

JavaScript 反应 JS DOM JSX

评论


答:

1赞 Artur Minin 11/8/2023 #1

要获取渲染的 div 的高度,您需要:

  1. 通过以下方式获取隐藏 div 的内容:ref
<div ref={hiddenContentRef} className='editor' style={{visibility:'hidden'}}>
  1. 使用 once is changed 遍历每个内部 div,获取每个 div() 的高度并将其存储到状态中:useLayoutEffectlinesitem.getBoundingClientRect().height
  const [linesHeight, setLinesHeight] = React.useState(new Array(lines.length).fill(0));

  React.useLayoutEffect(() => { 
     // Update the height of each line number to match the real-lines
      hiddenContentRef.current.childNodes.forEach(function(item, index){
        setLinesHeight(prevLines => ([...prevLines.slice(0, index), item.getBoundingClientRect().height, ...prevLines.slice(index + 1)]))
    });
  }, [lines]); 

然后,您将能够为每个行号设置适当的高度:

<div className='margin'>
  {lines.map((line,i)=>(<div style={{height: `${linesHeight[i]}px`}}>{(1+i).toString()}</div>))}
</div>

JSFiddle

评论

0赞 Micrified 11/8/2023
谢谢,这有帮助!再多待一会儿,你就会接受你的答案:)