提问人:Micrified 提问时间:11/8/2023 更新时间:11/8/2023 访问量:37
根据渲染时间信息(即行高)调整 React DOM 中的元素
Adjusting elements in the React DOM based on render-time information (i.e. line height)
问:
上下文
我使用 React JS 创建了一个简单的带有行号的文本编辑器。它应该只编辑纯文本(即不需要富文本编辑)。text-input 字段用 div 表示。打字时,我面临三个挑战:contentEditable
- 我希望每个换行符都显示行号。
- 我不想重新渲染到用户正在输入的 DOM,因为它会重置光标(请参阅此处的类似问题)。
- 我希望行号只出现在实际的换行符上。不适用于由于包含框的边界而环绕的线。
尝试的解决方案
- 为了显示行号,我在可编辑内容旁边创建了一列数字。我使用 React 为每行创建一个。✅
map
- 为了避免 DOM 重新渲染的问题,我曾经确保我的文本输入不会被重新渲染(并且光标位置不会丢失)。✅
React.useRef
- 为了确保行号只出现在实际换行符旁边,我尝试将文本输入镜像到隐藏的 div 中。这个想法是我可以将每一行插入到它自己的 div 中。然后,我可以获取每行 div 的高度,并使用它来设置行号 div 的高度,以便它们匹配(见下图): ❌
不幸的是,这目前并没有完全按照我想要的方式工作。我似乎无法弄清楚如何获取渲染的 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>
答:
1赞
Artur Minin
11/8/2023
#1
要获取渲染的 div 的高度,您需要:
- 通过以下方式获取隐藏 div 的内容:
ref
<div ref={hiddenContentRef} className='editor' style={{visibility:'hidden'}}>
- 使用 once is changed 遍历每个内部 div,获取每个 div() 的高度并将其存储到状态中:
useLayoutEffect
lines
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>
评论
0赞
Micrified
11/8/2023
谢谢,这有帮助!再多待一会儿,你就会接受你的答案:)
上一个:文档片段
评论