可以在不进行恒定重排的情况下动态调整高度的文本区域吗?

Rya*_*hel 7 html javascript css reflow reactjs

注意:据我所知,这不是重复项,因为使用contentEditablediv似乎不是一个很好的选择。它有很多问题(没有占位符文本,需要使用dangerouslySetInnerHTML技巧来更新文本,选择光标过于挑剔,其他浏览器问题,等等),我想使用文本区域。

我目前正在为我的React textarea组件执行以下操作:

componentDidUpdate() {
  let target = this.textBoxRef.current;

  target.style.height = 'inherit';
  target.style.height = `${target.scrollHeight + 1}px`; 
}
Run Code Online (Sandbox Code Playgroud)

这可以工作,并且允许文本区域在添加和删除换行符时动态地增加和缩小高度。

问题在于,每一次文本更改都会发生重排。这在应用程序中造成很多滞后。如果我在文本区域中按住一个键,则在附加字符时会有延迟和滞后。

如果我取消target.style.height = 'inherit';生产线,那么滞后就会消失,所以我知道这是由于这种不断回流造成的。

我听说设置overflow-y: hidden可能会消除持续的重排,但是在我看来并不是这样。同样,设置target.style.height = 'auto';不允许动态调整大小。

我目前已经开发了一种可行解决方案,但我不喜欢它,因为每次文本更改时它都是O(n)操作。我只计算换行的数量并相应地设置大小,如下所示:

// In a React Component

handleMessageChange = e => { 
  let breakCount = e.target.value.split("\n").length - 1;

  this.setState({ breakCount: breakCount });
}

render() {
  let style = { height: (41 + (this.state.breakCount * 21)) + "px" };

  return (
    <textarea onChange={this.handleMessageChange} style={style}></textarea>
  );
}
Run Code Online (Sandbox Code Playgroud)

Rya*_*hel 7

我认为三十点的建议可能是最好的。他链接的Material UI textarea有一个非常聪明的解决方案。

它们创建了一个隐藏的绝对定位的文本区域,该区域模仿实际文本区域的样式和宽度。然后他们将您键入的文本插入该文本区域并获取其高度。由于它的位置绝对正确,因此无需进行回流焊计算。然后,他们使用该高度作为实际文本区域的高度。

我不完全了解他们的代码在做什么,但是我根据自己的需要进行了最小化的重新设计,而且看起来效果很好。以下是一些摘要:

.shadow-textarea {
  visibility: hidden;
  position: absolute;
  overflow: hidden;
  height: 0;
  top: 0;
  left: 0
}
Run Code Online (Sandbox Code Playgroud)
<textarea ref={this.chatTextBoxRef} style={{ height: this.state.heightInPx + "px" }}
          onChange={this.handleMessageChange} value={this.props.value}>
</textarea>

<textarea ref={this.shadowTextBoxRef} className="shadow-textarea" />
Run Code Online (Sandbox Code Playgroud)
componentDidUpdate() {
  this.autoSize();
}

componentDidMount() {
  this.autoSize();
}
Run Code Online (Sandbox Code Playgroud)
autoSize = () => {
  let computedStyle = window.getComputedStyle(this.chatTextBoxRef.current); // this is fine apparently..?

  this.shadowTextBoxRef.current.style.width = computedStyle.width; // apparently width retrievals are fine
  this.shadowTextBoxRef.current.value = this.chatTextBoxRef.current.value || 'x';

  let innerHeight = this.shadowTextBoxRef.current.scrollHeight; // avoiding reflow because we are retrieving the height from the absolutely positioned shadow clone

  if (this.state.heightInPx !== innerHeight) { // avoids infinite recursive loop
    this.setState({ heightInPx: innerHeight });
  }
}
Run Code Online (Sandbox Code Playgroud)

有点骇人听闻,但似乎效果很好。如果任何人都可以改善或改善它,或者用更优雅的方法清理它,我将接受他们的回答。但是,考虑到Material UI使用它,这似乎是最好的方法,这是我到目前为止尝试过的唯一一种方法,它消除了在足够复杂的应用程序中造成延迟的昂贵的回流计算。

Chrome浏览器仅报告高度变化时发生一次重熔,而不是每次按键时。因此,当文本区域增大或缩小时,仍然存在30ms的滞后时间,但这比每次按键或更改文本都要好得多。使用此方法可以将滞后时间减少99%。