React標準の useRef と useEffect で実装する。
1. デフォルトのtextarea
HTMLの<textarea>は、デフォルトでは枠のサイズが固定されていて、あふれた文字は内部スクロールで表示される仕様になっている。
そのため、
- 文字全体の実際の高さ(見えていないスクロール領域を含む高さ)の取得
- その高さをtextarea自身のCSS(
height)に直接適用する
JavaScriptには要素の中身の総高さを取得できる scrollHeight というプロパティを利用します。
2. useRef と useEffect の役割
- useRef
- 役割:React管理下の特定のHTML要素(今回は
<textarea>)を直接捕まえるための「参照(ピン留め)」を作る - イメージ:対象のタグに目印のタグをつけておき、いつでも
ref.currentでそのDOM要素にアクセスできるようにする
- 役割:React管理下の特定のHTML要素(今回は
- useEffect
- 役割:画面の描画後や特定の値(State)が変わったタイミングで処理を実行する
- 今回の目的:「テキスト(入力内容)が1文字でも変わったら、高さを測り直して更新する」ために使う
3. 最小構成のコード例
import React, { useState, useRef, useEffect } from "react";
export default function AutoResizeTextarea() {
const [text, setText] = useState("");
// 1. textareaのDOMにアクセスするための参照を作成
const textareaRef = useRef(null);
// 2. textが更新されるたびに実行される処理
useEffect(() => {
const element = textareaRef.current;
if (!element) return;
// ① 一度高さをautoにリセットする
element.style.height = "auto";
// ② 中身の全体の高さ(scrollHeight)を取得し、heightにセットする
element.style.height = `${element.scrollHeight}px`;
}, [text]); // textが変更された時だけトリガー
return (
<textarea
ref={textareaRef}
value={text}
onChange={(e) => setText(e.target.value)}
rows={1} // 初期値は1行分に設定
placeholder="メッセージを入力..."
style={{
width: "100%",
resize: "none", // ユーザーの手動リサイズつまみを消す
overflow: "hidden", // スクロールバーを消す
boxSizing: "border-box",
}}
/>
);
}
4. 補足
-
element.style.height = "auto"がないと、「文字を増やしたときは伸びるのに、BackSpaceで文字を消しても縮まない」となる。
そのため、一度autoで高さをリセットしてからscrollHeightで高さを計り直す - CSSの指定
-
resize: "none": ユーザーが右下のつまみをドラッグして自由にサイズ変更できないようにする -
overflow: "hidden": 計算と再描画のわずかな隙間にスクロールバーがチラつくのを防ぐ
-