textareaをjsから徹底調教してオレオレエディタをつくるための情報まとめ

textarea を js で制御してオレオレ・テキストエディタを作ってみます、という点から制御方法を調べていきます。

同じ手口で input[type=text] もイケます、多分。IE には改行文字の問題があるため textarea の方が格段に難しくなります。

次の目標は designMode=on です。

したいこと

  1. カーソル位置・選択範囲の取得
  2. カーソル位置・選択範囲の変更
  3. カーソル位置にテキストの挿入
  4. 選択範囲のテキストの置換

textarea のカーソル位置等の取得・変更には selectionStart 等のプロパティがあります。これが使えればとても素直に実装できそうです。さてブラウザの対応状況はいかがでしょうか?

もしかしたらしたいこと、座標と矩形から…

  1. 座標から文字の index を取得
  2. カーソル位置の座標の取得
  3. 選択範囲の座標と矩形の取得
  4. 座標と矩形から選択範囲に相当する値を取得

ちなみに座標が絡む場合 TextRange が使えないと難しくなる。TextRange は IE が独自に実装し Opera だけが取り入れている。

これ以外のブラウザでは隠し要素を作って textarea と同じテキストフローになるようにした上でこれを測定している記事が出てくる。

Github では caretposition.js のコードが読みやすいかも。

カーソル位置・選択範囲の取得と更新

textareaのselectionStart, selectionEnd, setSelectionRangeとTextRangeの対応状況

ブラウザ対応バージョン
IE9+
IE TextRange4~10
Opera8+
Opera TextRange7 or 810.5
Firefox1+
Safari2.0.3+ または 1.3.2+
Chrome1+

IEでの実装方針

selectionStart, selectionEnd と互換性のある数値を返すようにします。カーソル位置・選択の変更の際も同様です。

IE8 以下については TextRange を使用します。

readonly 属性は不可

js から制御する場合 readonly 属性を設定しても良さそうです。しかしいざ設定すると改行の動きがおかしくなってしまい実用になりません。他のブラウザでは正しく改行するのでこれは IE だけの問題です。IE11 や WP10 でも確認しました。

この他にペーストや選択範囲をドラッグも出来なくなります。

IE9~11
ちょっとしたパッチ

selectionStart 等をサポートするがカーソルが末尾にいる際に textarea.value.length より大きい textarea.selectionStart を返すときがあります。これは他のブラウザと異なるため補正します。Edge(IE12) では修正されているため不要でした。

if( IE < 12 ){
 var len = textarea.value.length;
 return {
  from : textarea.selectionStart < len ? textarea.selectionStart : len,
  to   : textarea.selectionEnd   < len ? textarea.selectionEnd   : len
 }
}

また IE9~10 では以下の TextRange を使ったコードも動作しましたがパフォーマンスの良さそうな selectionStart を使うことにしました。

IE8以下

カーソル位置・選択範囲の取得に苦しめられましたが、これ以降はトントンと実装できました。

改行文字が異なるため変換する

ライブラリ内で textarea.value を読み出す際に \r\n を \n に変換して返り値を他のブラウザと合わせています。

val = textarea.value.split('\r').join('');

ちなみに、range.moveEnd, range.moveStart に与える引数は以上の変換を考慮し改行分の補正を加える必要はありません。他のブラウザと同じ値を与えれば意図した動作をします。

カーソル位置・選択範囲を求めるコード

selectionStart, selectionEnd と互換性があり、正しくキャレット位置を求めるコードは HowTo: Cross-Browser Cursor Position in Textareas に紹介されているものを元にしました。そのままでは \r を含む start 位置が変えるため \r の数だけ数字が小さくなるように手を入れました。

その後気づきましたがこの手のノウハウは Closure Library にばちっと収録されていますので、ソースを検索してみるのがいいですね。本件についてもブラッシュアップされたコードが収録されていました。 closure-library/closure/goog/dom/selection.js の goog.dom.selection.getEndPointsTextareaIe_ です。


ちなみに次のコードでは selectionStart, selectionEnd と互換性のある値を取ることができませんでした。

  1. javascript - IE's document.selection.createRange doesn't include leading or trailing blank lines - Stack Overflow の回答のうち Tim Down 氏によるコード
  2. HTML のテキストエリアでカーソル位置を得る - Yahoo!知恵袋

次の記事で図解されている改行を TextRange が取りこぼす問題が関係しています。

その前に、IEとNNでは選択範囲の実装の違いから共通化できない部分があります。

それは、IEのTextRangeオブジェクトで取得した範囲にもしラストに改行コード¥r¥nがあった場合それが含まれないのです。(視覚的な選択範囲には含まれています)

現象を確認していませんが懸念事項としてメモ

まぁこれをIE8で動かすと range.moveToPoint()でエラーが起きます。 IE8「未定義だよ!未定義だよ!」 ちなみにこの未定義のエラーが起きるパターンは私が調べた限りでは 行頭か行末にカーソルがある場合。(つまりテキストがない場合もエラーが起きます。)


以下はいずれ追記します。

スクロールについて

【黒魔術】テキストエリアのキャレット位置をjsで指定した時にスクロールバーを追従させる - Qiita, Fixed problem where setting the scrollTop attribute for textarea elements didn't scroll the textarea contents

モバイル対応

iOSで物理キーボードのキーイベントを拾う

HTMLで独自のソフトウェアキーボードを用意するケース

その他のバグ情報

http://blog.livedoor.jp/tzifa/archives/50799216.html