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

问题描述 投票:0回答:1

背景

我使用 React JS 创建了一个带有行号的简单文本编辑器。它应该只编辑纯文本(即不需要富文本编辑)。文本输入字段用

contentEditable
div 表示。打字时,我面临三个挑战:

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

尝试的解决方案

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

不幸的是,目前这并没有达到我想要的效果。我似乎不知道如何获取渲染的 div 的高度,然后将它们设置为行号。我尝试使用

useEffect
来运行 after DOM 渲染,但我无法找到如何“挂钩”到 div 数组并计算它们的高度。


最小代码示例

请参阅这个工作 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 reactjs dom jsx
1个回答
0
投票

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

  1. 通过
    ref
    获取隐藏div的内容:
<div ref={hiddenContentRef} className='editor' style={{visibility:'hidden'}}>
  1. 一旦
    useLayoutEffect
    更改,使用
    lines
    迭代每个内部 div,获取每个 div 的高度(
    item.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

© www.soinside.com 2019 - 2024. All rights reserved.