はじめに
入力テキストの量によってテキストエリアの高さが自動で伸縮する jQueryプラグイン 「autoResize」 というものがあります.
本エントリでは,このプラグインがどんな感じで動いているのかを,私が解釈できている範囲で解説してみます.
なお,参考にしているソースコードは version 1.04 です.
autoResizeプラグインとは?
冒頭で触れたような機能を,James Padolsey さん が jQueryプラグインとして動作するように実装したものです.
Inspired by Jason Frame’s method, I’ve created an animating ‘autoResize’ jQuery plugin.
jQuery plugin: ‘autoResize’ – James Padolsey
James さん自身は,Jason Frame さんの手法にインスパイアされたようです.(さらにその起源は Facebook で実装されているもののようです.)
テクニックの概略
autoResizeプラグインのテキストボックス高さ自動伸縮機能は,大まかに次のようなテクニックで実現されています.
- テキストボックスを値ごと複製し,見えないところに配置する
- 複製テキストボックスの値(入力テキスト)を基に,そのテキストが表す高さを計算する ← ここがミソ
- 得られた高さの値を元のテキストボックスに適用する
テクニックの詳細 (0)
ちょっとした気遣い.
25 26 | // Get rid of scrollbars and disable WebKit resizing: var textarea = $(this).css({resize:'none','overflow-y':'hidden'}), |
Webkit なブラウザ(Safari とか Chrome とか) では,テキストエリアをマウスドラッグでリサイズできるようになっていますが,それを取り除いています.
テクニックの詳細(1)
1.テキストボックスを値ごと複製し,見えないところに配置する
このステップについて.
31 32 | // Need clone of textarea, hidden off screen: clone = (function(){ |
51 | })(), |
テキストエリアの複製を,変数 clone としています.ここには jQuery オブジェクトが入ります.その詳細は以下.
34 35 36 37 38 39 40 41 | // Properties which may effect space taken up by chracters: var props = ['height','width','lineHeight','textDecoration','letterSpacing'], propOb = {}; // Create object of styles to apply: $.each(props, function(i, prop){ propOb[prop] = textarea.css(prop); }); |
テキストエリアとそこに入力されるテキストに関連するスタイル,要は後で取得する高さの値に影響を及ぼしうるスタイルですね,の情報を,変数 propOb に格納しています.
45 46 47 48 49 | return textarea.clone().removeAttr('id').removeAttr('name').css({ position: 'absolute', top: 0, left: -9999 }).css(propOb).attr('tabIndex','-1').insertBefore(textarea); |
元のテキストエリア (変数 textarea として前で宣言されています) を複製し(以下,複製テキストエリア),自身の手前に挿入しています (DOM的な意味で).
見えない状態でテキストエリアとしての有効性を保つ
複製テキストエリアを絶対位置配置とし,その水平位置をはるか彼方にセットしています.
また,先で格納した propOb 内のスタイル情報も適用されます.
誤動作を防止する
フォーム送信時のデータ重複やその他の誤動作を防止するために,複製テキストエリアの id,name の両属性が取り除かれ,また,Tab 操作でフォーカスされないように tabindex が -1 にセットされています.
そんな感じでいろいろセットされた複製テキストエリアを指す jQuery オブジェクトができあがりました.これが変数 clone から参照されることになります.
テクニックの詳細(2,3)
2.複製テキストボックスの値(入力テキスト)を基に,そのテキストが表す高さを計算する ← ここがミソ
3.得られた高さの値を元のテキストボックスに適用する
これらのステップはイベントハンドラとして定義され,対象となるテキストエリアで特定のイベントが発生するごとに呼び出されます.
53 | updateSize = function() { |
78 | }; |
テキストの量に応じた高さを取得する
56 | clone.height(0).val($(this).val()).scrollTop(10000); |
元のテキストエリアの値(入力されたテキスト)を,複製テキストエリアにコピーします.
そして,複製テキストエリアの高さを 0 とし,垂直スクロール位置を一番下に移動させています.(10000 という値はそういう解釈でいいでしょう,きっと.)
59 | var scrollTop = Math.max(clone.scrollTop(), origHeight) + settings.extraSpace, |
先の1行を実行すると,clone.scrollTop() の値をテキストの高さとして取得できるというわけですね.これが元のテキストボックスの高さに適用されることになります.ちなみに,テキストの高さがテキストエリアの元の高さ(変数 origHeight)より小さい場合には,origHeight の値がセットされるようになっています.
この辺,個人的になんだかイメージしにくかったので,次のような図を用意しました.
テキストの高さを適用する
60 | toChange = $(this).add(clone);" |
$(this) は元のテキストエリアのみを参照する jQuery オブジェクトですが,その参照先に複製テキストエリアも追加しています.(「参照」という表現でおk?)
67 68 69 70 | if ( scrollTop >= settings.limit ) { $(this).css('overflow-y',''); return; } |
この部分はオプション的機能で,上限の高さをセットしておくと,それ以上テキストエリアは伸びなくなります.
75 76 77 | settings.animate && textarea.css('display') === 'block' ? toChange.stop().animate({height:scrollTop}, settings.animateDuration, settings.animateCallback) : toChange.height(scrollTop); |
先で取得した 「入力テキストの高さ」 を元のテキストエリア(と複製テキストエリア)に適用します.
オプションでアニメーションするように指定されていれば,高さの変化がなめらかになります.
イベントリスナの追加
81 82 83 84 85 | textarea
.unbind('.dynSiz')
.bind('keyup.dynSiz', updateSize)
.bind('keydown.dynSiz', updateSize)
.bind('change.dynSiz', updateSize); |
キーアップ,キーダウン,内容変更のそれぞれのイベントに対してイベントリスナを追加しています.
ちなみに,イベントタイプの後についている .dynSiz は,「イベントの名前空間」です.詳細は以下を参照してください.
気になった点
- undoができないっぽい
- マウスの右クリックメニューとかでテキストをペーストした際にイベントが発生しない
おわりに
以上,入力テキストの量に応じてテキストエリアの高さが自動伸縮する jQuery プラグイン 「autoResize」 のしくみをざっと解説してみました.
いろいろ誤解な部分があると思うので,ツッコミお待ちしています.
