自作編
基本的な方針
- textareaのvalueの変更を検知する
- textareaのheightをautoにする
- textareaのscrollHeightを計測する
- textareaのheightにscrollHeightを代入する
textareaの初期値がautoであれば上記方針で基本的には問題ないです。
JavaScript(抜粋)
const TextArea = () => { const [ value, setValue ] = React.useState(''); const [ height, setHeight ] = React.useState(0); const textAreaRef = React.useRef(null); React.useEffect(() => { if (textAreaRef.current) { setHeight(0); // テキストエリアの高さを初期値に戻す } }, [value]); React.useEffect(() => { // 高さが初期値の場合にscrollHeightを高さに設定する if (!height && textAreaRef.current) { setHeight(textAreaRef.current.scrollHeight); } }, [height]); function handleChangeValue(value) { setValue(value); } return ( <textarea ref={ textAreaRef } value={ value } onChange={ (evt) => handleChangeValue(evt.target.value) } style={{ height: height ? `${ height }px` : 'auto' }} /> ); };
DEMO
実際に試してみるとわかるのですが、高さが一瞬初期値に戻るため、入力のたびにガクガクします。
基本的な方針はOKなのですが、これでは見栄えが良くありません。
応用的な方針
基本的な方針はそのままに、高さ計算用のDOMと表示用のDOMを分けてみます。
JavaScript(抜粋)
const TextArea = () => { const [ value, setValue ] = React.useState(''); const [ height, setHeight ] = React.useState(0); const textAreaRef = React.useRef(null); const invisibleTextAreaRef = React.useRef(null); React.useEffect(() => { if (invisibleTextAreaRef.current) { setHeight(invisibleTextAreaRef.current.scrollHeight); } }, [value]); function handleChangeValue(value) { setValue(value); } return ( <React.Fragment> <textarea // 表示用テキストエリア ref={ textAreaRef } value={ value } onChange={ (evt) => handleChangeValue(evt.target.value) } style={{ height: height ? `${ height }px` : 'auto' }} /> <textarea // 高さ計算用テキストエリア ref={ invisibleTextAreaRef } value={ value } onChange={ () => {} } tabindex="-1" // tabでフォーカスが当たらないようにする style={{ position: 'fixed', top: -999 }} // 見えない範囲へ移動 /> </React.Fragment> ); };
DEMO
ガクガクしなくなりました。一見問題なさそうです。
他人のふんどし編
応用的な方針だと、textareaを常に2つでワンセットで扱うため、単純に数が倍になってしまいます。
textareaをたくさん設置するページだと、無駄なtextareaまみれになってしまいますね。
また、幅、高さを調整する場合、計算用、表示用両方を同じ値にしなければならないので、Propsで幅、高さを受け取るなどの工夫も必要そうです。
面倒だけど、もろもろ調整するかと思っていたそのとき、便利なライブラリを教えてもらいました。
ソースを確認したところ、方針としては計算用、表示用のtextareaを用意するというものでしたが、計算用は1つだけ用意し、すべてのテキストエリアの高さを任せているようです。非常に合理的です。
結果として、今後はこちらのライブラリを活用していこうと思いました。