![](/img/trans.png)
[英]Increase the textarea height (without the scroll bar) dynamically as the user keeps typing
[英]Possible to have a dynamically height adjusted textarea without constant reflows?
注意:據我所知,這不是重復的,因為使用contentEditable
div 似乎不是一個好的選擇。 它有很多問題(沒有占位符文本,需要使用dangerouslySetInnerHTML
SetInnerHTML hack來更新文本,選擇光標很挑剔,其他瀏覽器問題等)我想使用文本區域。
我目前正在為我的 React textarea 組件執行以下操作:
componentDidUpdate() {
let target = this.textBoxRef.current;
target.style.height = 'inherit';
target.style.height = `${target.scrollHeight + 1}px`;
}
這有效並允許 textarea 在添加和刪除換行符時動態增長和縮小高度。
問題是在每次文本更改時都會發生回流。 這會導致應用程序出現很多滯后。 如果我在 textarea 中按住一個鍵,則會在附加字符時出現延遲和滯后。
如果我刪除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>
);
}
我認為三十多點的推薦可能是最好的。 他鏈接的Material UI textarea有一個非常聰明的解決方案。
他們創建了一個隱藏的絕對定位的 textarea,模仿實際 textarea 的樣式和寬度。 然后他們將您鍵入的文本插入該文本區域並檢索它的高度。 因為它是絕對定位的,所以沒有回流計算。 然后他們使用該高度作為實際 textarea 的高度。
我並不完全了解他們的代碼在做什么,但我已經根據我的需要進行了最小的重新利用,並且它似乎工作得很好。 以下是一些片段:
.shadow-textarea {
visibility: hidden;
position: absolute;
overflow: hidden;
height: 0;
top: 0;
left: 0
}
<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" />
componentDidUpdate() {
this.autoSize();
}
componentDidMount() {
this.autoSize();
}
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 });
}
}
有點hacky,但它似乎工作得很好。 如果有人可以體面地改進它或用更優雅的方法清理它,我會接受他們的答案。 但這似乎是考慮到 Material UI 使用它的最佳方法,而且它是迄今為止我嘗試過的唯一一種方法,可以消除在足夠復雜的應用程序中導致延遲的昂貴回流計算。
Chrome 僅在高度變化時報告回流發生一次,而不是在每次按鍵時發生。 所以當 textarea 增長或縮小時仍然有一個 30ms 的延遲,但這比每次擊鍵或文本更改都要好得多。 使用這種方法,滯后消失了 99%。
注意: Ryan Peschel 的回答更好。
原帖:我對 apachuilo 的代碼進行了大量修改以達到預期的效果。 它根據textarea
的scrollHeight
調整高度。 當框中的文本更改時,它將框的行數設置為minRows
的值並測量scrollHeight
。 然后,它計算文本的行數並更改textarea
的rows
屬性以匹配行數。 計算時該框不會“閃爍”。
render()
只調用一次,並且只改變了rows
屬性。
當我輸入 1000000(一百萬)行,每行至少 1 個字符時,添加一個字符大約需要 500 毫秒。 在 Chrome 77 中對其進行了測試。
CodeSandbox: https ://codesandbox.io/s/great-cherry-x1zrz
import React, { Component } from "react";
class TextBox extends Component {
textLineHeight = 19;
minRows = 3;
style = {
minHeight: this.textLineHeight * this.minRows + "px",
resize: "none",
lineHeight: this.textLineHeight + "px",
overflow: "hidden"
};
update = e => {
e.target.rows = 0;
e.target.rows = ~~(e.target.scrollHeight / this.textLineHeight);
};
render() {
return (
<textarea rows={this.minRows} onChange={this.update} style={this.style} />
);
}
}
export default TextBox;
雖然不可能消除所有回流——瀏覽器必須在某個時刻計算高度——但可以顯着減少它們。
根據 Paul Irish (Chrome 開發人員), elem.scrollHeight
是導致回流的屬性訪問和方法之一。 但是,有一個重要的注意事項:
僅當文檔已更改並使樣式或布局無效時,回流才會產生成本。 通常,這是因為 DOM 已更改(修改了類,添加/刪除了節點,甚至添加了像 :focus 這樣的偽類)。
這就是,對於純文本, textarea 實際上優於<div contenteditable>
。 對於 div,鍵入會更改innerHTML
,它實際上是一個Text
節點。 因此,以任何方式修改文本也會修改 DOM,從而導致回流。 在 textarea 的情況下,輸入只會改變它的value
屬性——沒有任何東西觸及 DOM,所需要的只是重新繪制,這(相對)非常便宜。 這允許渲染引擎緩存上述引用所指示的值。
由於瀏覽器緩存scrollHeight
,您可以使用“經典”建議 - 獲取該值並立即將其設置為實際高度。
function resizeTextarea(textarea) {
textarea.style.height = 'auto';
textarea.style.height = `${textarea.style.scrollHeight}px`;
}
每當值更改時使用該方法,這將確保 textarea 保持在不滾動的高度。 不要擔心屬性的連續設置,因為瀏覽器會一起執行這些(類似於requestAnimationFrame
)。
這適用於所有基於 WebKit 的瀏覽器,目前是 Chrome 和 Opera,很快也會是 Edge。 我認為 Firefox 和 Safari 有類似的實現。
就我個人而言,除非您寫小說,否則我無法想象像那樣閱讀所有這些換行符是一個太大的問題,但我不知道。 您可以嘗試根據擊鍵調整中斷次數。
沙箱在這里。
import React, { Component } from "react";
class TextBox extends Component {
state = {
breakCount: 0
};
handleKeyDown = e => {
if (e.key === "Enter") {
this.setState({ breakCount: this.state.breakCount + 1 });
}
// Note you will want something to better detect if a newline is being deleted. Could do this with more logic
// For quick testing of spamming enter/backspace key though this works.
if (e.key === "Backspace" && this.state.breakCount > 0) {
this.setState({ breakCount: this.state.breakCount - 1 });
}
};
render() {
const style = { height: 41 + this.state.breakCount * 21 + "px" };
return <textarea onKeyDown={this.handleKeyDown} style={style} />;
}
}
export default TextBox;
僅使用 react 的內置功能的“現代” hooks- apporach將是useRef和useLayoutEffects 。 這種方法在瀏覽器中進行任何渲染之前更新由值更改觸發的 textarea 的高度,從而避免 textarea 的任何閃爍/跳躍。
import React from "react";
const MIN_TEXTAREA_HEIGHT = 32;
export default function App() {
const textareaRef = React.useRef(null);
const [value, setValue] = React.useState("");
const onChange = (event) => setValue(event.target.value);
React.useLayoutEffect(() => {
// Reset height - important to shrink on delete
textareaRef.current.style.height = "inherit";
// Set height
textareaRef.current.style.height = `${Math.max(
textareaRef.current.scrollHeight,
MIN_TEXTAREA_HEIGHT
)}px`;
}, [value]);
return (
<textarea
onChange={onChange}
ref={textareaRef}
style={{
minHeight: MIN_TEXTAREA_HEIGHT,
resize: "none"
}}
value={value}
/>
);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.