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

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

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

初期設定

まず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を利用してtextareaの要素を取得します。

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


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

vue側では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>