レイヤーベースのプレースホルダ付き入力欄を作る

変更履歴

2009/03/26
IE6 不具合対策として、input 要素と同一 font-size をラベルに割り当てる処理を無くし、パラメータでラベルの style を指定できるようにしました。(理由はソースのコメント参照)

prototype.js

jQuery

昨日作ったものはテキストボックスからフォーカスが外れたときにvalue属性に値をセットしていたので、submitした時に表示されている文字列がそのまんま送信されてしまいます。これはいけません。
色々と考えたのですが、親要素のformタグを探してonsubmitイベントでテキストボックスのvalueをクリアするという方法しか思いつきませんでした。
でもこれだと直接submitするときはいいけど他のJavaScriptからvalueを参照されたときにはまた同じことが起こるんだよなー。クライアント側での入力チェックとかに支障をきたすなー。うーんうーん。

JQueryでプレースホルダつき入力欄その2 - syttruの日記

そこが一番の懸念ですよね。
なので使いどころが限られるなぁと思ってましたが、INPUT 要素を汚さず実現できれば問題ないのでは..ということで試してみました。

プレースホルダ要素を INPUT 要素の上に重ねる

jQuery を使って input 要素に、

<input/>

relative なコンテナでラップして、absolute なラベルをアペンドします。

<span class="container" style="position:relative">
    <input/>
    <div class="label" style="position:absolute;top:1;left:4">input here...</div>
</span>

で、以下のようなラベルの表示・非表示制御を追加します。

  • div.label のクリックで、input へ focus
  • input の focus で div.label の非表示
  • input の blur で input が未入力状態なら div.label の表示

プラグインの定義

(function($j){
    var placeHolder = function(target,cfg){
        var o=this;
        var c=o.cfg=$j.extend({
            target:target,
            text:'input here...',
            top:1,
            left:4,
            labelCSS:null //追加
        },cfg);
        o._build();
    };
    $j.extend(placeHolder.prototype,{
        _build:function(){
            var o=this,c=o.cfg;
            c.container=c.target
                .wrap('<span style="position:relative;"/>')
                .parent();
            c.label=c.container
                .append('<div class="placeHolder-label" \
                            style="position:absolute;color:#aaa;">'+
                    c.text+'</div>')
                .find('> :last-child')

//IE6 の場合 body{font-size:80%} とか input{font-size:80%} とかしてると
//$('input').css('font-size')ででかい値が px 単位で取れてしまい
//プレースホルダの文字がばかでかくなってしまう。
//   http://h2ham.seesaa.net/article/106524977.html
//で、currentStyle.fontSizeの値をあてるとbody{font-size:80%}の相対的な
//サイズになるので今度は小さくなる。
//input要素自体のサイズを変更することはあまり無いと思うのでここでは何も
//せず、パラメータで上書き可能にする

                .css({
//                  'font-size':c.target.css('font-size'),     //削除
                    'font-family':c.target.css('font-family'),
                    top:c.top,
                    left:c.left
                });

            if(c.labelCSS)c.label.css(c.labelCSS);  //追加

            c.label.click(function(){
                o.toggleLabel(true);
                c.target.focus();
            });
            c.target.focus(function(){o.toggleLabel(true)});
            c.target.blur(function(){o.toggleLabel(false)});
            o.toggleLabel();
        },
        toggleLabel : function(isFocus){
            var o=this,c=o.cfg;
            c.label[isFocus||c.target.val()!=''?'hide':'show']();
            return o;
        }
    });
    $j.fn.placeHolder = function(cfg){
        var o=this;
        return o.each(function(idx){
            new placeHolder(o.eq(idx),cfg);
        })
    }   
})(jQuery);
実行
jQuery(function($j){
    $j('input,textarea').placeHolder({
        text:'search'
    })
});

サンプルページ

Safariスクリーンショット

SafariFirefox の textarea の場合のみプレースホルダの表示位置がずれます。
span 要素内の textarea の配置ということで、レンダリングにブラウザ間の差異があるようです。
(offset 系の値も同様)
以下のように表示位置補正処理を追加します。
( c.label.click(function... の上に追加します。)

if(c.target.attr('tagName')=='TEXTAREA' &&
  (jQuery.browser.safari || jQuery.browser.mozilla))
    c.label.css({
        top:c.label.position().top
            -(c.target.outerHeight()-c.label.outerHeight())
    });

サンプルページ

Safariスクリーンショット

直りました。

所感

input 要素周りに付属物が増えてしまいますが、submit や validate の都度プレースホルダを意識した value 属性参照をするよりはましかと思われます。