😉

【JavaScript】textareaに改行制限を実装する方法(予測変換(IME)対応)

2024/11/24に公開

はじめに

とあるプロジェクトで入力可能な文字数と行数が決まっているtextareaを実装する必要がありました。
文字数制限はmaxLengthを使って簡単に解決できましたが、行数制限はなかなか難しかったので、解決方法を記事として書こうと思います。

この記事で実装するtextareaの条件

  • 5行目まで入力可能。
  • textareaの標準のスクロールバーとリサイズ機能は非表示にする。
index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>textarea 行数制限</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <textarea rows="5"></textarea>
    <script src="app.js"></script>
  </body>
</html>
style.css
textarea {
  background-color: #f4f4f4;
  width: 300px;
  height: 96px;
  font-size: 16px;
  line-height: 1.2;
  padding: 15px;
  resize: none;
  overflow: hidden;
}

textarea:focus {
  outline: none;
  border: 1px solid blueviolet;
}

解決手順

1. 改行文字(\n)を数えて改行を制限する。

textareaタグ内でenterボタンを押すと改行文字(\n)が入るので、\nを数えて5個以上なら、入力できないように塞ぐコードを作成しました。

app.js
const textarea = document.querySelector("textarea");

const MAX_ROW = 5; //入力可能な最大の行数
textarea.addEventListener("input", (e) => {
  const lines = textArea.value.split("\n");

  if (lines.length > MAX_ROW) {
    textarea.value = lines.slice(0, MAX_ROW).join("\n");
  }
});

しかし、この方法では、Enterキーを押さずに長い文章を入力すると、\nが挿入されずに自動改行され、5行目以上も入力できてしまう問題がありました。


改行文字で制限した結果


2. Element.offsetHeightとElement.scrollHeightを使う。


参考:https://ja.javascript.info/size-and-scroll

clientHeightの説明

  • 要素の内部の高さ(ピクセル単位)を取得するためのプロパティ。(読み取り専用)

scrollHeightの説明

  • 要素のコンテンツ全体の高さ(ピクセル単位)を取得するプロパティ。(読み取り専用)

またMDNの説明によると、「要素の内容が垂直スクロールバーを表示することなく収まる場合、その scrollHeight は clientHeightと等しくなります。」ということがわかります。
このプロパティを使用して、もし scrollHeightclientHeightより大きい場合、textareavalueの末尾を削除し、入力できないように見えるコードを作成しました。

app.js
const textarea = document.querySelector("textarea");

textarea.addEventListener("input", (e) => {
  const { scrollHeight, clientHeight } = e.target;
  if (scrollHeight > clientHeight) {
    textarea.value = textarea.value.slice(0, -1); // 末尾を削除
  }
});

しかし、この方法も問題がありました。

予測変換を使わない英語などでは問題ありませんでしたが、予測変換を使用して長い文を入力すると、既に入力されている末尾の文字が削除され、5行目を超えて入力できてしまう現象が発生しました。


scrollHeightを制限した結果

実装方法(結果)

3. 予測変換(IME)の入力イベントを監視するために、compositionstartcompositionend を組み合わせる。

調べた結果、IME入力イベントを監視できるイベントリスナーがありましたので、これらを使ってみました。

compositionstart

  • ユーザーが文字入力を開始したときに発生するイベント。
  • 特に、日本語入力(IME)や他の複雑な入力方式で変換や候補選択が始まるタイミングで発火。

compositionend

  • ユーザーが文字入力の変換を確定したときに発生するイベント。
  • 特に、日本語入力(IME)や他の複雑な入力方式で変換や候補選択が完了したタイミングで発火。

以下は予測変換(IME)入力も対応したコードになります。

app.js
let isComposing = false; // IME 変換中をトラッキング

//テキストエリアのテキストを最大行数以内に制限する関数
const truncateTextToMaxLines = (textarea, clientHeight) => {
  const originalValue = textarea.value;
  while (textarea.scrollHeight > clientHeight) {
    textarea.value = textarea.value.slice(0, -1); // 末尾を削除
  }
  return textarea.value.length > 0 ? textarea.value : originalValue;
};

//テキストエリア入力イベント関数
const handleTextareaInput = (e) => {
  if (isComposing) return; // IME 入力中は処理をスキップ

  const targetTextarea = e.target; //テキストエリアの要素を取得
  const { scrollHeight, clientHeight } = targetTextarea;
  if (scrollHeight > clientHeight) {
    targetTextarea.value = truncateTextToMaxLines(targetTextarea, clientHeight);
  }
};

const textarea = document.querySelector("textarea");
textarea.addEventListener("compositionstart", () => {
  isComposing = true;
});
textarea.addEventListener("compositionend", (e) => {
  isComposing = false;
  handleTextareaInput(e);
});
textarea.addEventListener("input", (e) => {
  handleTextareaInput(e);
});

問題なく予測変換も対応できるようになりました!

このコードは、CodePenでも確認できます。

Discussion