textareaの選択範囲を取得し、前後に文字列を挿入する

/web/javascript

Note: この記事は、3年以上前に書かれています。Webの進化は速い!情報の正確性は自己責任で判断してください。

textareaの中で選択した文字列の前後に、任意の文字列を挿入するJavascript。流れとしては、まず選択領域の始点と終点の位置を取得し、その境界を基準にしてtextareaの文字列全体を分割。そして加工して組みなおす。

始点と終点の位置は、Mozillaおよび(最近の)Opera/Safariでは容易に取得できる。問題はIE。IEのTextRangeオブジェクトは、後のコンテクストを見て処理を行うことが難しいため、特にtextarea内の文字列を操作したい場合は厄介です。

逆に言えば、始点と終点の位置さえ判ればなんとかなるので、いろいろ試してみてなんとか形に出来ました。

Sample

<p> | <a> | <strong>

memo: サンプルのテキストは、以前書いた記事「環境は人を造るか?」より抜粋。

Script

  1. function getAreaRange(obj) {
  2. var pos = new Object();
  3.  
  4. if (isIE) {
  5. obj.focus();
  6. var range = document.selection.createRange();
  7. var clone = range.duplicate();
  8.  
  9. clone.moveToElementText(obj);
  10. clone.setEndPoint( 'EndToEnd', range );
  11.  
  12. pos.start = clone.text.length - range.text.length;
  13. pos.end = clone.text.length - range.text.length + range.text.length;
  14. }
  15.  
  16. else if(window.getSelection()) {
  17. pos.start = obj.selectionStart;
  18. pos.end = obj.selectionEnd;
  19. }
  20.  
  21. return pos;
  22. // alert(pos.start + "," + pos.end);
  23. }
  24. var isIE = (navigator.appName.toLowerCase().indexOf('internet explorer')+1?1:0);

ここが要。選択領域の位置を取得している部分。既存の選択領域とは別の選択領域を用意し、新しい選択領域でtextareaを選択。始点あるいは終点を操作し、2つの選択領域の文字数をカウントすることで位置を割り出す。

note: clone.setEndPoint( 'EndToEnd', range );」で、「cloneの終点をrangeの終点に合わせる」の意。

  1. function surroundHTML(tag, obj) {
  2. var target = document.getElementById(obj);
  3. var pos = getAreaRange(target);
  4.  
  5. var val = target.value;
  6. var range = val.slice(pos.start, pos.end);
  7. var beforeNode = val.slice(0, pos.start);
  8. var afterNode = val.slice(pos.end);
  9. var insertNode;
  10.  
  11. if (range || pos.start != pos.end) {
  12. insertNode = '<' + tag + '>' + range + '</' + tag + '>';
  13. target.value = beforeNode + insertNode + afterNode;
  14. }
  15.  
  16. else if (pos.start == pos.end) {
  17. insertNode = '<' + tag + '>' + '</' + tag + '>';
  18. target.value = beforeNode + insertNode + afterNode;
  19. }
  20. }

境界の座標が判れば、後は「分割 → 追加 → 再統合」の流れでOK。この場合は選択領域だけでなく、まだ選択していない状態でもキャレット(文字の入力位置を示すポインタ)の位置に挿入できるようになっている ...が、この操作は関数getAreaRange()にて「obj.focus()」していないとIEでは巧くいかなかったりする。注意。

参照: Experts Exchange, IE - textarea caret position

Note: スパム対策が面倒なので、コメント投稿を廃止しました。以前のコメントは残します。
ご意見・ご要望はtwitter@sigwygかはてブコメントにて。

30 Comments

ねこ wrote:
初めまして。今作っているサイトに、こちらのスクリプトを使用させて頂きましたので、お礼も兼ねてご挨拶に参りました。希望通りの動作が得られてとても助かっています。どうもありがとうございました。 2008–01–11 21:15
Sig. wrote:
こういうコメントは嬉しいな。
お役に立てて良かったです!
コメント thx! 2008–01–12 16:51
trooper wrote:
初めまして。いやーすばらしいスクリプトです。
TinyMCE等のリッチテキストエディタでいろいろやろうと考えてましたが、もっと軽量で
スタイリッシュなものをと望んでいたところ、このサイトを見つけました。
これをもとに、いいものを作りたいと思います。本当にありがとうございました。 2008–05–29 13:00
Sig. wrote:
お役に立てたようで嬉しいです。
どうぞ心ゆくまで使い倒してやってください^^b 2008–06–02 20:45
もんか wrote:
上の方たちと同様、
こちらのスクリプトがとても参考になりました。
ありがとうございます。 2008–06–26 15:40
Sig. wrote:
どういたしまして~!
いや、意外と需要あるんですね、コレ。
モチベ上がってきた...! 2008–06–28 05:07
Hoged wrote:
Good!!!!!!!!! 2008–07–07 18:13
ひげろん wrote:
これは助かります。
使わせてもらいます、本当にありがとうございました。 2008–07–12 23:40
Sig. wrote:
Thanks all! 2008–07–15 07:23
k wrote:
目からウロコでした。

var target = document.getElementById(obj);

この要素取得については別の関数にしたかったので、
勝手ながら若干改造しました^^ 2008–08–02 20:30
くま wrote:
すばらしいコードを公開して頂き、
ありがとうございます。
早速、使わせて頂きます。 2008–10–24 22:48
KOBA wrote:
今、ちょうどこの情報がほしかったので、希望とまったく同じものが見つかってとても感謝しています。
ありがとうございました。 2008–12–16 15:35
taro wrote:
現在探していたコードがやっとみつかりました。
ありがとうございました。 2009–01–05 16:49
Anonymous wrote:
自分も作ろうと思ったんですがIEだけ出来なかったので助かりました!
参考にさせていただきます。
どうもありがとうございます。 2009–02–28 22:18
ds wrote:
ありがとう。 2009–04–07 12:42
ちこ wrote:
まさしく欲しかったコードが見つかり、ほんとにありがたいです。
これを参考させていただきますね。
ありがとうございました。 2009–06–02 14:24
Anonymous wrote:
使えないorz 2009–06–17 13:38
Sig. wrote:
それだけじゃ何にもできないんだぜ? 2009–06–18 03:37
研修生 wrote:
研修中に参考にさせていただき、ありがとうございます。
本当に困っていたので助かりました^-^b 2009–06–22 17:19
ぺけぺけ wrote:
本日の締め切りに向け、デッドラインと戦っている中でこのサイトを見つけました。
心より感謝です…。ありがとうございます! 2009–09–17 07:23
ok.2nd wrote:
こんにちは。
個人または家族で使える自宅用のWebポータルシステム「MyHome Portal」をオープンソースとして公開しています。
このMyHome Portalのカレンダーのスケジュール入力画面で、スクリプトを利用させていただきました。
http://ok2nd.blog87.fc2.com/blog-entry-129.html
Chrome、Safariでtextarea内の文字列選択がうまくいかない問題がやっと解決できました。
ありがとうございました。 2009–10–03 07:32
370 wrote:
IEもMozillaも余すとこなく参考にさせていただきました。
助かりました。ありがとうございました。 2010–02–15 20:09
ハルヒト wrote:
すばらしいものをありがとうございます!
ありがたく使わせていただきます!! 2010–03–26 12:07
ok.2nd wrote:
こんにちは。

文字列を挿入後、カーソル(キャレット)が、textarea内の挿入文字の後ろに残る修正版を作成しました。継ぎ接ぎ的なロジックになってしまいましたが、とりあえず、公開しました。
http://ok2nd.blog87.fc2.com/blog-entry-246.html 2010–12–21 18:14
thanks. wrote:
まさに探し求めていたものでした。
この機能がこんな少しのスクリプトで実現できるとは驚きです。

ありがたく使わせていただきます。 2011–02–01 16:25
izmm wrote:
はじめまして。
こちらのソースですが、textarea以外のタグ(divやbody)で入力可能にしている場合にも使いたいのですが、
対応可能でしょうか。 2011–03–09 15:40
Sig. wrote:
こんちは!
contentEditableな選択領域は、textareaの選択領域の場合と取り方が違うんです。
まずそのままじゃ使えないよ、というのと、
タグの重複とか考えるといろいろ面倒なので、素直にexecCommand()使ったほうが良いと思います。 2011–03–10 02:04
izmm wrote:
ありがとうございます。

なるほど。
execCommandですね。
はじめて知りました^^;

実は以下の処理を使っていまして、
http://d.hatena.ne.jp/okinaka/20090727/1248671860
これを使ってテキストエリアが対象の場合はイケました。
こちらを「contentEditableな選択領域」にも効くようにしたいんです。
$("body").insertAtCaret(HTMLが入った変数hoge);
で失敗してます。

トライ中の対象はMTのリッチテキストエリアです。
いろいろとやってみたのですが、
アドバイスできそうな事があれば、伺いたくお願い申し上げます。 2011–03–10 18:12
izmm wrote:
自己解決しました。

iframe内のドキュメントの取り方が間違ってました。
純粋なJsとJQueryで記述が微妙に異なるところがネックになってました。
こちらの記事もとても助かりました。
今後も参考にさせていただきます。
ありがとうございました。 2011–03–11 10:32
wrote:
すばらしい!!
まさに探していたものでしたので、ありがたく使わせて頂きます。
動作テストしていて気になったのが何も選択せずに連続してタグを挿入するとキャレットがずれるバグ
は「ok.2nd」さんの所で見事に解決されていましたのでとても参考になりました。

この場を借りてお二人に感謝いたします。
ありがとうございました。 2012–02–21 14:48