コフス技術ブログ

<textarea>要素に入力された特定の文字列に線を引く際のアイデア

内容が入力された<textarea>要素において、特定の文字列に線を引きたい状況を想定してみます。例えば以下のGIFでは入力済みの<textarea>要素において、文字列ダミーに赤線が引かれています。

通常純粋な<textarea>要素ではこのような装飾を行うことは出来ませんが(装飾を行えるライブラリを除く)、単純に赤線だけの要素を別途用意し、重ねることで恰も線が引かれているように見せることは容易に実現出来そうです。

こういった表現を行う際に有効なテクニックとしてMirror Textareaが知られています。

Mirror Textareaとは

そもそも恰も線を引いているように見せるには、特定の文字列の矩形情報さえ知っていればその矩形情報を元に線を引く要素を生成し位置を重ねることで実現出来ます。

矩形情報はcreateRange()で容易に取得出来ますが、<textarea>要素ではcreateRange()を使用出来ません。これは<textarea>要素のvalueはDOMではなくプロパティであるためです。createRange()はDOMに対してのみ動作します。

上記を踏まえ、Mirror Textareaは名前の通り<textarea>要素を<div>要素でミラーし、<textarea>要素では使用できないcreateRange()をミラー要素側で用いて、特定のindex間に存在する文字列の矩形情報を取得するテクニックです。
<textarea>要素で実現できないなら代わりの要素を用意してしまえば良いという発想ですね。

Mirror Textareaでは主に以下のような事を行います。

  1. <textarea>要素を<div>要素でミラー(複製)します。
    1. 複製する際はスタイルを同じにします。
    2. エンドユーザーに見せる必要は無いのでvisibility: hiddenを指定します。またwhite-space: pre-wrapを指定することで改行を反映させます。
    3. 複製元のvalueが更新されるたびにtextContentを更新します。
    4. 必須ではありませんが、リサイズした時はミラー要素にもサイズを同期させると良いです。
  2. <textarea>要素の下にミラー要素を重ねます。実際にユーザーが操作するのは<textarea>要素なのでz-indexには注意します。
  3. 矩形情報を取得したい文字列の開始indexと終了indexを用意します。
  4. <textarea>要素では使用できなかったcreateRangeがミラー要素では使用できるので、開始indexと終了indexを元にcreateRange()を用いて範囲を取得します。
  5. 取得した範囲はRange()オブジェクトなので、getBoundingClientRect()を用いて矩形情報を取得します。
  6. 取得した矩形情報はDOMRectオブジェクトなので、この段階で特定の文字列のtop, left, width, height情報を得ることが出来ました。
  7. 得た情報を元に線を引く要素を生成し、<textarea>要素の下に重ねます。
  8. 後は線を引く要素に任意のスタイルを追加して完了です。

各要素の重なり順は以下のような感じになります。

実装例