HTMLのtextareaの高さはrows属性によって行を設定することはできますが、
一度設定すると変更を行うことができません。本文書ではvue.jsを利用してユーザが入力した行によってtextareaの高さを自動拡張、自動収縮する機能を作成します。

textareaの高さの自動調整についてはvue-textarea-autosize-masterというパッケージがあるので中身を参考にさせてもらいました。

手元の環境で動作確認できるようにcdnを利用して作成しています。

textareaの高さが比較的短い場合は問題になりませんが、textareaの高さがブラウザの高さを超えた場合少しスクロールをして文字を入力すると入力のカーソルがブラウザの一番下に表示されるように自動でスクロールが行われるといった動作になります。textareaが高さが長い場合には適していないので注意してください。

初期設定

まずvue.jsが動作することを確認するためにブラウザ上にHelloを表示させます。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Vue</title>
  </head>
  <body>
    <div id="app">
      <h1>{{ tweet }}</h1>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
    <script>
     new Vue({
        el: "#app",
        data() {
          return {
            tweet: "Hello",
          };
        },
      })
    </script>
  </body>
</html>

画面にHelloが表示されるのを確認します。

Helloを表示
Helloを表示

textareaの設定

textareaタグを追加して、v-modelでtweetを設定します。textareaの高さはstyleタグを利用して行うのでvue側からstyleを制御できるようにバインディングを行います。


<textarea v-model="tweet" :style="styles"></textarea>

バインドのために設定を行ったstylesはcomputedプロパティを使ってvueに追加します。高さの初期値は60pxに設定しておきます。つまりcomputedプロパティを通してheightに60pxを設定していることになります。


 new Vue({
    el: "#app",
    data() {
      return {
        tweet: "Hello",
        height:"60px",
      };
    },
    computed:{
      styles(){
        return {
          "height": this.height,
        }
      }
    },
  })

ブラウザで確認すると高さが60pxのtextareaが表示されます。

 textareaの高さの確認
textareaの高さの確認

textareの高さを調べる方法

textareaを自動調整するためにはtextareaの高さが知っておく必要があります。高さを取得するためにscrollHeightを利用します。textareaのscrollHeightを知るためにはtextareaの要素を取得する必要があります。vue.jsではrefを利用することで直接要素にアクセスを行なって情報を取得することができます。ここでもrefを利用してtextareaの要素を取得します。

textareaにref属性を追加し識別するための任意の名前を設定します。ここではareaという名前をつけています。refを設定した要素はvue側から直接アクセスすることが可能となります。


<textarea v-model="tweet" ref="area" :style="styles"></textarea>

vue側ではthis.$refsに先程textareaに設定したareaを使いthis.$refs.areaでrefを設定したtextareaの要素を取得することができます。本当にここまでの設定で要素の情報が取得できているのか確認するためにライフサイクルフックmountedで要素が取得できていることを確認してみましょう。


mounted(){
  console.log(this.$refs.area);
}

ブラウザのデベロパーツールのコンソールを確認すると下記が表示されます。

refsを利用して要素を取得
refsを利用して要素を取得

高さはscrollHeightを追加することで取得できます。64と表示されます。


mounted(){
   console.log(this.$refs.area.scrollHeight);
}

高さを取得するタイミング

高さはtextareaに文字を入力・削除した時に取得したいためvueのwatchを利用します。入力した文字を保存するデータプロパティtweetを監視し文字の入力・削除を検知します。


watch:{
  tweet(){
    console.log(this.$refs.area.scrollHeight);
  },
},

上記の設定が完了したら文字の入力を行ってください。文字を入力する度にコンソールにメッセージが表示されます。

3行目まで文字を入力しても64のメッセージが表示されますが、4行目に入るとメッセージに表示される値が76になることを確認してください。初期のtextareaに入りきらないため高さが増えたことを表しています。textareaの右側にはスクロールバーが表示されます。

textareaの領域が拡大した
textareaの領域が拡大した

さらに行を進めていくとメッセージに表示される数字は行の数に比例して大きくなります。今後は行を削除してください。増えた数字が減少することになります。

この数字を監視してcomputedプロパティを通して設定することができれば、行に合わせてtextareaの領域を自動で変更することができるというイメージが湧くかと思います。

resizeメソッドの追加

次に高さを設定するresizeメソッドを作成していきます。下記のコードの中で”この行だけでOK?”とコメントを付けましたが、通常であればこの1行であれば動作するように思うかもしれません。しかし、この行だけでは正常に動作しません。この1行を設定すると文字を入力するたびにコンソールに出力したheightの値が4ずつ増えます。この問題を解消するため以下の2点を設定する必要があります。

1点目はnexTickを設定する必要があります。nextTickを利用することでDOMの更新が完了する前のscrollHeightの値を取得することを避けることができます。nextTickを使う場合は、引数のcallback関数の中でscrollHeightの取得の処理を行います。

2点目は、nextTickの処理の前にthis.heightの値を設定する必要があります。ここではautoに設定しています。

この2点を設定することでresize関数は先ほどの問題を解消することができます。


methods:{
  resize(){
    this.height = this.$refs.area.scrollHeight + 'px';//この行だけでOK?
    this.height = "auto";
    this.$nextTick(()=>{
      this.height = this.$refs.area.scrollHeight + 'px';
    })
  }
},
auto以外の60pxや0でも動作します。しかしaaaなどの文字列を入れると動作しません。

最後に追加したresizeメソッドをmounted時とwatchの中に登録します。mount時にresizeを実行するためtextareaの高さはresize関数の中のthis.heightの高さに設定されます。


 new Vue({
    el: "#app",
    data() {
      return {
        tweet: "Hello",
        height:"60px",
      };
    },
    computed:{
      styles(){
        return {
          "height": this.height,
        }
      }
    },
    watch:{
      tweet(){
        this.resize();
      },
    },
    methods:{
      resize(){
        this.height = "auto";
        this.$nextTick(()=>{
          this.height = this.$refs.area.scrollHeight + 'px';
        })
      }
    },
    mounted(){
      this.resize();
    }
  })

最初は下記のようにHelloが表示されます。

デフォルトの高さ
デフォルトの高さ

文字を入力すると行の高さに合わせてtextaregの高さが増えます。

行が増えると高さが増える
行が増えると高さが増える

今度は増えた状態から行を削除すると高さが減ります。

行が減ると高さが自動で変わります。
行が減ると高さが自動で変わります。

このようにvue.jsを使うことでtextareaの高さを自動調整できるコードを作成することができます。作成したコードは下記の通りです。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Vue</title>
  </head>
  <body>
    <div id="app">
      <h1>{{ tweet }}</h1>
      <textarea v-model="tweet" ref="area" :style="styles"></textarea>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
    <script>
     new Vue({
        el: "#app",
        data() {
          return {
            tweet: "Hello",
            height:"60px",
          };
        },
        computed:{
          styles(){
            return {
              "height": this.height,
            }
          }
        },
        watch:{
          tweet(){
            this.resize();
          },
        },
        methods:{
          resize(){
            this.height = "auto";
            this.$nextTick(()=>{
              this.height = this.$refs.area.scrollHeight + 'px';
            })
          }
        },
        mounted(){
          this.resize();
        }
      })
    </script>
  </body>
</html>