テキストエリアの高さを自動調整する
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 2
◆ そういうメソッドあればいいのに
テキストエリアの高さって固定にしてユーザが勝手に調整すれば良いと思ってましたが ちょっと自動で広げたり縮めたりしたかったのでやってみました
困ったのは意外とブラウザごとに動きが違うということ
IE はともかく Chrome と Firefox はこの辺りならさすがに揃ってるとは思ったのですが そうは行きませんでした
ちなみに IE/Edge はテキストエリアの右下を掴んでユーザがサイズ調整する機能がありませんでした
確認時に手動でサイズ変更しようとして初めて気づきました
HTML5 になるよりずっと前からある当たり前機能だと思っていたのですが そうではなかったんですね
読んでみてなんでこうなってるの?と思えるところばっかりだったので一応珍しいくらいにコメントいっぱいです
clientHeight はテキストエリア自体の高さで scrollHeight はスクロールする分も含めた高さです
テキストエリアが 100px でも改行がいっぱいあって 1000px 分のテキストがあれば scrollHeight は 1000px になります
必要以上の高さがあって下に空白スペースのあるテキストエリアはこの 2 つの値が一緒です
スクロールバーが表示されていると clientHeight のほうが小さくなります
実際のテキストエリアの高さを調整するために設定するのは style.height プロパティです
これは clientHeight と一緒ではありません
clientHeight は padding まで含めた高さですが height は box-sizing によって変わります
その box-sizing の設定によって padding や border などをどう計算するか変えるのだと分岐とか処理が面倒なのと将来的にもっといろいろ考慮することが増えた時に大変なので 今回は実際の差分を使うようにしました
最初の方の diff 変数に差分のピクセル数が入っています
scrollHeight を ◯px にしたいときは ◯ - diff を height に指定すると scrollHeight が ◯px になります
しかし 余分な高さがあるときにはどれだけ縮めればスクロールバー出ないギリギリの値なのかがわかりません
なので 一度スクロールバーが絶対に出るくらいに縮めた上で scrollHeight を取得します
そうすればスクロールバーがいらない最低の高さがわかります
ですが 例外もあります
スクロールバーが出ている場合 スクロールバーが出ている状態でのコンテンツの高さ分が scrollHeight の値です
右に長くて折り返しが起きる場合 スクロールバーのありなしで折り返し位置が異なります
スクロールバーがある分狭くなり早めに折り返されて スクロールバーがないときよりも行数が増える場合があります
結果として scrollHeight に合わせて広げてみたら下に隙間ができるということが起きます
大抵の場合はちょっとした隙間ですが 折り返し数が多くなってくると何十行分も差がでてしまうことだってありえます
ここからは徐々に縮めていって スクロールバーが出たらその直前の値がスクロールバーを出さない最低の高さとわかります
今回は極端に何十何百 px もないという前提で 1px ずつ縮めています
例えば scrollHeight が 500px だったら次は 500/2=250px にしてスクロールバーがあるか確認して あったら (250+500)/2=375px で試して まだ出ていないなら 250/2=125px で試すのように二分探索とかでも良いと思います
ただ こうする場合はちょっと面倒で 大きく縮めたときにスクロールバーがでていた場合が特殊です
スクロールバーが出ている状態で広げるのと スクロールバーがでていない状態で縮める場合で 同じ高さにしてもスクロールバーの有無が変わります
スクロールバーがでている状態で広げると スクロールバーがある前提での折り返しで必要高さが決まります
スクロールバーを消してみればちょうど収まってスクロールバーいらないかもしれない という状態でも試してくれないので 一度スクロールバーがない状態にしてから縮めないと本当にスクロールバーが出ない最低の高さを求められません
言ってることがわかりづらいかもしれないので図のような説明も入れてみます
必要な高さは 5 行分です
高さを縮めてスクロールバーを出すと スクロールバーで 2 文字分とすると 8 文字のところで折り返しが起きます
「||」 のところがスクロールバーです
これだと 10 行必要になりましたね
本当は 5 行分の高さでいいのに この状態だと 5 行や 6 行の高さではスクロールバーが出ます
一度 10 行分の高さにして再計算させると スクロールバーがないので 5 行にしてもスクロールバーが出ません
こういう面倒くささがあります
Chrome では
ですが Firefox だと動かなくて調べてみると 十分に小さい 0 px の高さにしたときの scrollHeight の計算方法が違っていました
Chrome だと高さが小さいときはスクロールバーがあるのでスクロールバーがある状態での高さです
しかし Firefox だと 0 px の場合の scrollHeight はスクロールバーがない状態での高さでした
なので縮めていくという必要はなくていきなり求めてる高さに設定できていました
また Chrome のときの縮めすぎた場合に一度大きめにして再計算させてから と言った部分も違っていてスクロールバーありなしと高さの計算方法が Chrome と Firefox では違ってるみたいです
Firefox の計算方法が スクロールバーがあるのにない前提の高さになっていて正しい動きなのか心配なところもありますが とりあえず Firefox は最初の高さ設定のところまでとしています
テキストエリアになにか入力するごとに自動で高さが調整されます
手動でサイズを変えてその後で何か入力すれば 伸びたり縮んだりします
困ったのは意外とブラウザごとに動きが違うということ
IE はともかく Chrome と Firefox はこの辺りならさすがに揃ってるとは思ったのですが そうは行きませんでした
ちなみに IE/Edge はテキストエリアの右下を掴んでユーザがサイズ調整する機能がありませんでした
確認時に手動でサイズ変更しようとして初めて気づきました
HTML5 になるよりずっと前からある当たり前機能だと思っていたのですが そうではなかったんですね
コード
最終的にこんな関数でできました読んでみてなんでこうなってるの?と思えるところばっかりだったので一応珍しいくらいにコメントいっぱいです
function adjustTextAreaHeight(ta) {
const initial_height = parseFloat(getComputedStyle(ta).height)
// height と clientHeight の差分 px
// padding 量だけど box-sizing で変わるので実際の差分から取得
const diff = ta.clientHeight - initial_height
// height を 0 にした状態の scrollHeight が必要な高さ
setHeightPx(0)
const noscroll_height = ta.scrollHeight - diff
setHeightPx(noscroll_height)
// Firefox は高さ 0 のときにスクロールバーなしの高さなのでここで終わり
// 続けるとちゃんと動かない
if (navigator.userAgent.includes("Firefox")) return
// Chrome はスクロールバーあり状態の必要高さなので右端折返しがあるとその分隙間がある
// scrollHeight と clientHeight が異なるところまで縮める
let height = noscroll_height
while (ta.scrollHeight === ta.clientHeight) {
setHeightPx(--height)
}
const final_height = height + 1
// いったんスクロールバーない状態にしないと折り返しあり状態になっている
setHeightPx(noscroll_height)
// 再計算
ta.scrollHeight
setHeightPx(final_height)
function setHeightPx(height) {
ta.style.height = height + "px"
}
}
説明
実際の高さを求めるために使うプロパティは clientHeight と scrollHeight ですclientHeight はテキストエリア自体の高さで scrollHeight はスクロールする分も含めた高さです
テキストエリアが 100px でも改行がいっぱいあって 1000px 分のテキストがあれば scrollHeight は 1000px になります
必要以上の高さがあって下に空白スペースのあるテキストエリアはこの 2 つの値が一緒です
スクロールバーが表示されていると clientHeight のほうが小さくなります
実際のテキストエリアの高さを調整するために設定するのは style.height プロパティです
これは clientHeight と一緒ではありません
clientHeight は padding まで含めた高さですが height は box-sizing によって変わります
その box-sizing の設定によって padding や border などをどう計算するか変えるのだと分岐とか処理が面倒なのと将来的にもっといろいろ考慮することが増えた時に大変なので 今回は実際の差分を使うようにしました
最初の方の diff 変数に差分のピクセル数が入っています
scrollHeight を ◯px にしたいときは ◯ - diff を height に指定すると scrollHeight が ◯px になります
高さを求める
基本的には clientHeight を scrollHeight の高さにすればスクロールバーがなくなりますしかし 余分な高さがあるときにはどれだけ縮めればスクロールバー出ないギリギリの値なのかがわかりません
なので 一度スクロールバーが絶対に出るくらいに縮めた上で scrollHeight を取得します
そうすればスクロールバーがいらない最低の高さがわかります
ですが 例外もあります
スクロールバーが出ている場合 スクロールバーが出ている状態でのコンテンツの高さ分が scrollHeight の値です
右に長くて折り返しが起きる場合 スクロールバーのありなしで折り返し位置が異なります
スクロールバーがある分狭くなり早めに折り返されて スクロールバーがないときよりも行数が増える場合があります
結果として scrollHeight に合わせて広げてみたら下に隙間ができるということが起きます
大抵の場合はちょっとした隙間ですが 折り返し数が多くなってくると何十行分も差がでてしまうことだってありえます
ここからは徐々に縮めていって スクロールバーが出たらその直前の値がスクロールバーを出さない最低の高さとわかります
今回は極端に何十何百 px もないという前提で 1px ずつ縮めています
効率よくするなら
場合によっては遅くなるので 効率良く縮めていくよう改良したほうが良いと思います例えば scrollHeight が 500px だったら次は 500/2=250px にしてスクロールバーがあるか確認して あったら (250+500)/2=375px で試して まだ出ていないなら 250/2=125px で試すのように二分探索とかでも良いと思います
ただ こうする場合はちょっと面倒で 大きく縮めたときにスクロールバーがでていた場合が特殊です
スクロールバーが出ている状態で広げるのと スクロールバーがでていない状態で縮める場合で 同じ高さにしてもスクロールバーの有無が変わります
スクロールバーがでている状態で広げると スクロールバーがある前提での折り返しで必要高さが決まります
スクロールバーを消してみればちょうど収まってスクロールバーいらないかもしれない という状態でも試してくれないので 一度スクロールバーがない状態にしてから縮めないと本当にスクロールバーが出ない最低の高さを求められません
言ってることがわかりづらいかもしれないので図のような説明も入れてみます
スクロールバーの有無
入力されているデータは 10 個の a が 5 行ですaaaaaaaaaa
aaaaaaaaaa
aaaaaaaaaa
aaaaaaaaaa
aaaaaaaaaa
必要な高さは 5 行分です
高さを縮めてスクロールバーを出すと スクロールバーで 2 文字分とすると 8 文字のところで折り返しが起きます
aaaaaaaa||
aa ||
aaaaaaaa||
aa ||
aaaaaaaa||
aa ||
aaaaaaaa||
aa ||
aaaaaaaa||
aa ||
「||」 のところがスクロールバーです
これだと 10 行必要になりましたね
本当は 5 行分の高さでいいのに この状態だと 5 行や 6 行の高さではスクロールバーが出ます
一度 10 行分の高さにして再計算させると スクロールバーがないので 5 行にしてもスクロールバーが出ません
こういう面倒くささがあります
Chrome では
Firefox の場合
Chrome はこれまでの方法でいろいろ試してもちゃんと求めてる高さにできましたですが Firefox だと動かなくて調べてみると 十分に小さい 0 px の高さにしたときの scrollHeight の計算方法が違っていました
Chrome だと高さが小さいときはスクロールバーがあるのでスクロールバーがある状態での高さです
しかし Firefox だと 0 px の場合の scrollHeight はスクロールバーがない状態での高さでした
なので縮めていくという必要はなくていきなり求めてる高さに設定できていました
また Chrome のときの縮めすぎた場合に一度大きめにして再計算させてから と言った部分も違っていてスクロールバーありなしと高さの計算方法が Chrome と Firefox では違ってるみたいです
Firefox の計算方法が スクロールバーがあるのにない前提の高さになっていて正しい動きなのか心配なところもありますが とりあえず Firefox は最初の高さ設定のところまでとしています
デモ
試せるページですテキストエリアになにか入力するごとに自動で高さが調整されます
手動でサイズを変えてその後で何か入力すれば 伸びたり縮んだりします
wrap が off なら
今回は 1 行が折り返される前提でしたが wrap="off" なら単純に改行数から行数がわかるので height の指定をなくして rows を指定するだけでできそうですa.oninput = function (eve) {
a.rows = this.value.split("\n").length
a.style.height = ""
}
COMMENT
コメント一覧 (2)
-
- 2019/08/07 21:38
-
//再計算〜
の下にある
ta.scrollHeight
はどのような意味があるのでしょか?
素人ですいません
-
- 2019/08/07 22:24
-
>>1
あまり覚えてないのですが 見た感じスタイルを再計算させるためかと思います
Chrome では最適化のため スタイルを変えても JavaScript の処理が終わってブラウザがレイアウト計算などの処理を始めるまでは内部的に計算されていない状態です
ときどき JavaScript 実行中に即時計算してもらいたいときがあるので そういうときには DOM のサイズを求める系の操作を行えば計算できます
***Height 系プロパティにアクセスしたり getClientRects メソッドを呼び出したりなどです
ちょっとしたハックみたいなものです
IE だと Chrome のような最適化はされてないのか 常に更新されていたはずなのでこういう一見無意味な処理は不要です
Firefox はちょっと覚えてません
今回の例だと 少し上のコメントに 「いったんスクロールバーない状態にしないと折り返しあり状態になっている」 とあるので noscroll_height を設定したあとに いったん再計算してスクロールバーなしにしたあとで final_height を設定しないと折返しが残ったままになってたんだと思います