User forum-FC2BLOG-Info-Edit Template-Post-Edit-Upload-LogOut
よく見かけるクロージャーの例として次のカウンターがあります。
■ カウンタークロージャー var counter = (function(){ var id = 0; return function(){ return id++} })(); // この counter 関数を次のように実行すると、その都度異なる値が返されるので、 // ユニークカウンターとして使うことが出来ます。 /* 1 度目 */ counter(); // 返値は 0 /* 2 度目 */ counter(); // 返値は 1 ・・・・
このカウンタークロージャーは、外からカウンター値を弄れないため、まさにユニークな「変えられない」カウンターとして使えることから、一部では推奨されています。(『Javascript 第 5 版』等)
確かに、この counter 関数を任意のコードに組み込んでその中でカウンターとして使えば、任意のコードが呼び出された回数を確実に測定することが出来ます。(但し、そのコードを再読み込みすればゼロに戻ってしまいますが・・・)
上を踏まえて、奇数 / 偶数をトグルで返すクロージャーを作ってみました。
■ 奇数/偶数トグルクロージャー var state = (function(){ var odd = 1; return function(){ return odd++ % 2 == 1 ? true : false } })() この state 関数を次のように実行すると交互に true / false が返されます。 /* 1 度目 */ state(); // 返値は true /* 2 度目 */ state(); // 返値は false /* 3 度目 */ state(); // 返値は true /* 4 度目 */ state(); // 返値は false ・・・・
ここに false を偶数、true を奇数と読み替えれば、state 関数は交互に偶数と奇数を返すものとなります。しかも、その値を外から操作することは出来ません。
これをコード内で利用すれば、例えば起動回数が奇数回か偶数回かを返して、場合分けを行うことが出来ます。
さて、以上の 2 つのクロージャーから、何故これらが単なる関数ではなく「クロージャー」と言われるのか、その所以を学習しようと思います。
これらの 2 つの関数に共通することは、入れ子になった関数がグローバル変数に返されていることです。そのため、外側の関数の実行が終了しても、入れ子になったそれぞれの関数に対するグローバルスコープからの参照が生き続けます。
こうしてグローバル環境から、いつでも入れ子になった関数にアクセスできることになります。
なお、関数がリテラル表記されていることは本質には何ら関係ありません。次のように書いてもクロージャーであることに何ら替わりはなく、同一の挙動をするからです。リテラル表記の方が簡潔に同一のことを表現できるに過ぎません。
function state (){ var odd = 1; return function(){ return odd++ % 2 == 1 ? true : false } } var log = state(); /* 1 度目 */ log() // result:true /* 2 度目 */ log() // result:false /* 3 度目 */ log() // result:true /* 4 度目 */ log() // result:false ・・・・
ここで、まずはっきりさせなければならないことは、「 クロージャー 」と言われる部分はどこなのか、ということです。恥ずかしながら、counter 関数や state 関数がクロージャーなのか、それとも入れ子になった関数がそれなのか、しばらく理解しないまま時が経過してしまったので、ここで明らかにしたいのです。
実は、改めて Javascript 第 5 版を読み直し、また非常に分かりやすいサイト クロージャと高階関数 をじっくり読んで、やっと理解出来たのは、つい最近のことなのです。
「 クロージャーとは、その関数が定義された環境への参照を持った関数のこと 」───上記サイト『クロージャと高階関数』のこの定義が曇った頭をすっきりさせてくれました。またよくよく読み返してみると、Javascript 第 5 版 において 「 クロージャーとしての入れ子型の関数 」(p.142)と表現されているではありませんか!
つまりクロージャーは外側の関数ではなく、その中にある入れ子になった関数です。「入れ子になった関数を定義した環境」とは、外側の関数の変数とその有効範囲(スコープ)を意味するわけで、上の 2 つの例では共に、入れ子になった関数が定義された環境とは、グローバル環境下にある関数の中に存在するローカル環境です。
さて、入れ子になった関数はよく使われますし、その関数が外側の関数環境への参照をもっていることも決して少なくありません。例えば、次のように外側の関数の引数や内部の変数を、内側の関数から参照するようなコードを書けば、内側の関数は 「 定義された環境への参照を持った関数 」 になります。つまり内側の関数はクロージャーです。
// 関数 sample はグローバル環境にある。 function sample(x){ // グローバル関数 sample 直下に、環境 Lc1 が格納されている。 var y = 1; // 環境 Lc1 には x、y、z の 3 つの変数がある。 // 環境 Lc2 から環境 Lc1 の中にある x と y を参照している。 var z = function(){ return y + x++; /*環境 Lc2*/}; return z(); }; /* 1 度目 */ sample(2) // result:3 /* 2 度目 */ sample(2) // result:3 /* 3 度目 */ sample(2) // result:3 ・・・・
しかし、いくらクロージャーがあっても、それだけではグローバル環境から内側の関数は参照出来ません。
上の例では、確かに内側の関数から外側の関数への参照( 参照 Lc2 → Lc1 )は存在しますが、グローバル環境から内側の関数への参照( 参照 G → Lc2 )が存在しません。 Lc1 への参照を持ったクロージャー(それは Lc2 環境にある)に対して、グローバル環境 G からアクセス出来なれば、外側の関数が終了すればそれと共にクロージャーもガーベージコレクションされてしまいます。
この点こそがポイントです。グローバル環境からクロージャーを参照する何らかの措置(例えば Lc2 環境からの return 値をグローバル環境に返す)を施して、グローバル環境から内側の関数にアクセス出来るようにすれば、外側の関数実行が終わった後においても、グローバル環境からクロージャーへの参照を利用して、結果として Lc1 環境へのアクセスが可能となります。
模式的に書けば、G → Lc2 → Lc1 ( → は左側の環境から右側の環境への参照を表す) となるとき、Lc2 環境の関数がクロージャーになります。
多くのクロージャーサンプルは、内側の関数からの return 連鎖を利用してグローバル変数と内側の関数をリンクさせていますが、グローバル環境からの内部関数への参照を設定することが、クロージャーを効果的に利用する「 秘策 」となります。
このエントリイで言及し掲載した slideToggleEx プラグインは不完全でした。完全版はエントリイ No. 733 をご覧ください。
jquery.js の自作プラグイン slideToggleEx を実際に各エントリイ内で随時利用するためには、簡便な利用方法を作っておく必要があります。こうした自らの需要に基づいて以下の方法をまとめました。
n は起終点を意味し次の通りの意味を持ちます。
0:ボックスの中心、1:左上端、2:右上端、3:右下端、4:左下端、
5:上辺、6:右辺、7:下辺、8:左辺
これだけ設定すれば、トリガーである button をクリックすると、n で指定した位置を起終点として、トリガーボタンの直後に配置された要素が slideToggleEx 適用対象要素となり、隠蔽されたり表示されるようなコード( エントリイ末尾に掲載 )を書きました。
「 これだけ 」と言っても結構たくさんあるため、最初は面倒に感じますが、直ぐに慣れますし、実際に使ってみると決して大変な手間ではありません。
上の 5 つだけで、slideToggleEx プラグインが簡単に使えるようにするために、もう1つ該当部分の HTML 文を特別な構造にする必要があります。これが 6 つ目にして最後の要件となります。その要件とは起動ボタンの直ぐ後に slideToggleEx 対象要素があるということです。
そうでない場合には 個々に slideToggleEx メソッドを呼び出すコードを書くことになります。
該当部分の HTML を以下のいずれかにする必要があります。(いずれのケースもボックスの左上を起終点とする場合で例示します。)
■ケース 1 <div><button class="fireSlideToggleEx-1">起動ボタン</button></div> <div>・・・slideToggleEx対象要素・・・</div> ■ケース 2 <button class="fireSlideToggleEx-1" style="display:block;width:120px;">起動ボタン</button> <div>・・・slideToggleEx対象要素・・・</div> ※ 2 行目の div 要素は別に div でなくてもブロック要素ならば何でも構いません。
ケース 1 は一般的な button タグの使い方の場合です。つまり button タグを inline 要素として配置する場合の HTML 文です。この場合には button タグの親要素の next sibling ブロック要素が slideToggleEx メソッドの適用対象となるようにコードを作りました。
一方ケース 2 は button タグをブロック要素として配置する場合です。この場合には当該のボタンタグの next sibling ブロック要素が slideToggleEx メソッドの適用対象となるようにコードを作りました。
このような当然と思われるHTML文構成にしておけば、運用に当たってどのようにしたかを思い出すのも容易ですし、仮に構造を忘れても直感的に思い出すことが可能となります。
プラグインは position 無指定版と指定版の 2 種類を作りました。どちらも全く同様の動きをしますが、後者の方がコード全体のバランスがよいかもしれません。
当初 position 指定版を作成し、アニメ適用対象に position:relative をその都度指定していました。top と left をアニメ対象とするので、アニメ対象要素には static 以外の position 指定が為されていなければならないためです。しかし、slideToggleEx プラグインの運用を重ねるに連れ、その都度指定することが非常に面倒であることに気がつきました。
そこで改めて position 無指定版を作成したのですが、それが完成してから、ふと position 指定をコードで行えばよいことに気がつきました。そして、仮にアニメ対象に position 指定がされていない場合でも、相対指定となるよう、Javascript コードによって強制的に指定することにしました。
結果として、回り道をしてしまったのですが、このようなケアレスミスを犯すこと自体が、まだまだコード開発に不慣れである証明かもしれません。
こうして、今実際に使っている slideToggleEx プラグインメソッドは最初に作った版を改訂した position 指定版となりました。
なお、アニメ継続時間と easing 関数は、初期値を設定する slideToggleEx.opts オブジェクトにおいて、1 秒に設定し、easing は無指定としています。opts オブジェクトを変えれば、自在に継続時間と easing 関数を変更できますが、使う際にその都度 duration と easing 関数を変更する必要はないと考え、(1) 指定するボタンタグと (2) 指定されるブロックタグ及び (3) slideToggleEx-n クラス名を指定するだけで使えればよいと判断しました。
以下のコードリストの表示/隠蔽には当然ながら、slideToggleEx を適用しています。
▼ver not use "position:relative/absolute" /* jQuery plugin : slideToggleEx() Release at 2009.8.21, ver2 at 2009.9.8*/ (function($){ var ver="absolute animation, non position assignment"; $.fn.slideToggleEx = function( type, duration, easing, fn ){ $target=this; var o = $.extend({},$.fn.slideToggleEx.opts); // 初めてそのインスタンスから呼ばれたか、インスタンスが変わった場合のみ if(!o.target || o.target[0]!==$target[0]) { $.extend(o,{ orig : o, // 当面使わないが念のために $.fn.slideToggleEx.opts をバックアップ target : $target, // jQuery インスタンスへの参照を登録する。 // ボックスを show する時に初期のマージン値を使用するので記録しておく。 initMTL:{marginTop:$target.css("margin-top"),marginLeft:$target.css("margin-left")}, /* ボックスのマージン辺までの外枠サイズをゲットする。 * 但し、jquery.js の 圧縮ファイルでは outerWidth(true) のように引数を指定しても * margin 辺までの外寸を取得しない。そこでやむなく引数を付けずに border 辺までの値を * 取得してから、それに margin 値を加算して margin 辺までの外寸を取得する。*/ oH : $target.outerHeight() + parseInt($target.css("margin-top")) + parseInt($target.css("margin-bottom")), oW : $target.outerWidth() + parseInt($target.css("margin-left")) + parseInt($target.css("margin-right")), // プラグイン起動回数が奇数か偶数かを記録するプロパティ odd : false }); /* メモリ上の opts オブジェクトにこの上で作成したばかりの o オブジェクトを統合 * して、初期値として保存する。同じインスタンスに対する 2 度目以降のプラグイン * 起動時には o オブジェクトに opts オブジェクトが転写され、かつこの if 条件は * falseとなるので if ブロックは履行されない。こうして効率的なコード進行となる。 * また、2 度目以降のプラグイン起動が異なるインスタンスに対して行われた場合には * 2 番目の if 条件 o.target[0]!==$target[0] が true となるので、if ブロックが履行される。 * こうして o オブジェクトも $.fn.slideToggleEx.opts オブジェクトも新しい * インスタンスの諸属性値を格納する。*/ $.fn.slideToggleEx.opts = $.extend($.fn.slideToggleEx.opts,o); } /* 奇数回目か偶数回目か、起動回数を記録する。ここでは $.fn.slideToggleEx.opts * オブジェクトを利用して slideToggleEx メソッドの実行が終わった後も * odd 値を残しておくことがポイントである。*/ o.odd = $.fn.slideToggleEx.opts.odd = !$.fn.slideToggleEx.opts.odd; o.hidden = $target.is(":hidden"); // アニメ対象が隠れているかどうか o.duration = duration || o.duration; // 第 2 引数か opts プロパティの利用 // 第 3 引数か opts プロパティの利用。duration がオブジェクトの時には空。 o.easing = typeof duration !=="object" ? (easing || o.easing) : ""; // 第 4 引数か opts プロパティの利用。duration がオブジェクトの時には空。 o.complete = typeof duration !=="object" ? ($.isFunction(fn) && fn || o.complete) : ""; // アニメ用の CSS オブジェクトを作成する関数 function makeAnimCSS(type,showhide){ type = new Number(type); if (showhide === false) { // 隠蔽されている時のアニメ用 CSS 作成 return type==0 ? {marginTop:o.oH/2+"px", marginLeft:o.oW/2+"px"}:// center type==1 ? {marginTop:0,marginLeft:0}: // 左上端 type==2 ? {marginTop:0,marginLeft:o.oW + "px"}: // 右上端 type==3 ? {marginTop:o.oH+"px", marginLeft:o.oW+"px"}:// 右下端 type==4 ? {marginTop:o.oH+"px", marginLeft:0}: // 左下端 type==5 ? {marginTop:0}: // 上辺 type==6 ? {marginLeft:o.oW+"px"}:// 右辺 type==7 ? {marginTop: o.oH+"px"}:// 下辺 {marginLeft:0}; // 左辺 // 表示されている時のアニメ用 CSS 作成 // type 値が 5 と 7 の時にはトップ値のみ、6 と 8 の時にはレフト値のみ // 0 ~ 4 の時にはトップ値とレフト値を使用する。 } else return (type==5 || type==7) ? {marginTop:o.initMTL.marginTop} : (type==6 || type==8) ? {marginLeft:o.initMTL.marginLeft} : o.initMTL; } if (type > 8) // エラー処理 (function(){ alert("第 1 引数は 0~8 だけが指定できます。\nやり直してください。\n"+ "slideToggleEx( 0~8, 継続時間, easing関数, アニメ後実行関数)"); return; })(); else { // type 値が 0 ~ 8 の時 // アニメ用 CSS の最終作成( margin 以外のプロパティ値からなるオブジェクトに、 // makeAnimCSS 関数で作ったオブジェクトを併合する) var obj =$.extend( {}, // margin 以外のプロパティ群 ( type==5 || type==7 ) ? {} : o.animWidth, ( type==6 || type==8 ) ? {} : o.animHeight, {opacity:"toggle"} ); /* 偶数回目の起動か、アニメ対象が隠蔽時の起動の時には、makeAnimCSS の * 第 2 引数を true に、奇数回目の起動か、アニメ対象が表示されている時には、 * makeAnimCSS の第 2 引数を false にする。 * これによりアニメ用 CSS 値を二通りの場合に対応させて作成する。*/ $.extend(obj,(!o.odd || o.hidden) ? makeAnimCSS(type,true) : makeAnimCSS(type,false)); return this.animate(obj, o.duration, o.easing,o.complete); // アニメーション起動 } } // End of $.fn.slideToggleEx function /* デフォルト値を slideToggleEx メソッドの opts プロパティに設定しておく。 * このオブジェクトは slideToggleEx メソッドの実行が終わった後の情報記録装置 * としても機能する。*/ $.fn.slideToggleEx.opts = { duration:1000, easing:null, complete:function(){}, animHeight : { // 高さに係るアニメ CSS の既定値 height:"toggle",marginTop:"toggle",marginBottom:"toggle",paddigTop:"toggle", paddingBottom:"toggle",borderTopWidth:"toggle",borderBottomWidth:"toggle" }, animWidth : { // 幅に係るアニメ CSS の既定値 width:"toggle",marginLeft:"toggle",marginRight:"toggle",paddigLeft:"toggle", paddingRight:"toggle",borderLeftWidth:"toggle",borderRightWidth:"toggle" } }; })(jQuery);
▼ver use "position:relative" // jQuery plugin : slideToggleEx() Release at 2009.8.21, verup 2009.9.9 (function($){ var ver="not absolute but relative animation on position relative"; // top と left プロパティによって起終点を操る position relative バージョン $.fn.slideToggleEx = function( type, duration, easing, fn ){ var $target = this; var o = $.extend({},$.fn.slideToggleEx.opts); // 初めてそのインスタンスから呼ばれたか、インスタンスが変わった場合のみ if(!o.target || o.target[0]!==$target[0]) { $.extend(o,{ orig : o, target : $target, oH : $target.outerHeight() + parseInt($target.css("margin-top")) + parseInt($target.css("margin-bottom")), oW : $target.outerWidth() + parseInt($target.css("margin-left")) + parseInt($target.css("margin-right")), odd : false }); $.extend($.fn.slideToggleEx.opts,o); if ($target.css("position") ==="static") $target.css({position:"relative",top:"0",left:"0"}); } o.odd = $.fn.slideToggleEx.opts.odd = !$.fn.slideToggleEx.opts.odd; o.hidden = $target.is(":hidden"); o.duration = duration || o.duration; o.easing = typeof duration !=="object" ? (easing || o.easing) : ""; o.complete = typeof duration !=="object" ? ($.isFunction(fn) && fn || o.complete) : ""; function makeAnimCSS(type,showhide){ // アニメCSSプロパティを作成する関数 var type = new Number(type); var plmn = showhide ? "-=" : "+="; // showhide == true → "-=" return type==0 ? {top: plmn + o.oH/2 +"px", left: plmn + o.oW/2 +"px"} : // center type==1 ? {top: plmn + 0, left: plmn + 0} : // 左上端 type==2 ? {top: plmn + 0, left: plmn + o.oW +"px"} : // 右上端 type==3 ? {top: plmn + o.oH +"px", left: plmn + o.oW +"px"} : // 右下端 type==4 ? {top: plmn + o.oH +"px", left: plmn + 0} : // 左下端 type==5 ? {top: plmn + 0, left: plmn + 0} : // 上辺 type==6 ? {top: plmn + 0, left: plmn + o.oW +"px"} :// 右辺 type==7 ? {top: plmn + o.oH +"px", left: plmn + 0} :// 下辺 type==8 ? {top: plmn + 0, left:plmn + 0} : {top:plmn + 0, left:plmn + 0} ;// 左辺 } if (new Number(type) > 8) // エラー対処 (function(){ alert("第 1 引数は 0~8 だけが指定できます。\nやり直してください。\n"+ "slideToggleEx( 0~8, 継続時間, easing関数 )"); return; })(); else { var obj = (!o.odd || o.hidden) ? makeAnimCSS(type,true) // 偶数回目の起動か、または要素が隠蔽されている時の CSS 作成 : makeAnimCSS(type,false); // 奇数回目の起動か、要素が表示されている時の CSS 作成 $.extend( obj, // top と left だけのCSSオブジェクトに幅と高さ関係値も追加 ( type==5 || type==7 ) ? {} : o.animWidth, // 上辺か下辺の時には幅方向CSSは対象外 ( type==6 || type==8 ) ? {} : o.animHeight, // 右辺か左辺の時には高さ方向CSSは対象外 {opacity:"toggle"} ); // 透明度もアニメ対象プロパティとする return this.animate(obj, o.duration, o.easing, o.complete); // アニメ起動 } }; // End of $.fn.slideToggleEx // 初期値設定とプロパティ記憶装置としての機能のために // ※ これは position 無指定版と指定版に共通する同一のものです。 $.fn.slideToggleEx.opts = { duration:1000, easing:null, complete:function(){}, animHeight : { height:"toggle",marginTop:"toggle",marginBottom:"toggle",paddigTop:"toggle", paddingBottom:"toggle",borderTopWidth:"toggle",borderBottomWidth:"toggle" }, animWidth : { width:"toggle",marginLeft:"toggle",marginRight:"toggle",paddigLeft:"toggle", paddingRight:"toggle",borderLeftWidth:"toggle",borderRightWidth:"toggle" } }; })(jQuery);
▼このコードは、メソッドプラグインではありませんが、プラグインとセットで include ファイルに登録しておくと重宝します。これがないと fireSlideToggleEx-n のクラス指定を しても next sibling 要素などを slideToggleEx の適用対象とすることは出来ません。 /* regist click Event to fire slideToggleEx() for nextBlock * クリックイベント起動ボタンと slideToggleEx 適用対象を登録する。 * これも position 無指定版、指定版に共通するコードです。*/ 1:$(function(){ // 当然 DOM が Ready されてから起動する必要があります。 2: var duration = 1000, easing = null,fn = function(){}, clk = {}; 3: function func(i){ 4: $.each(clk[i],function(){ // target オブジェクトは slideToggleEx 対象の個々のノード 1 つだけを格納する。 5; var $target={}; // $target オブジェクトの j プロパティに slideToggleEx 対象ノードを格納する。 // fireSlideToggleEx-x クラス指定されたボタン要素がブロック要素の場合には // next siblingを block 以外の inline 要素の場合には親要素の // next sibling 要素を slideToggleEx 対象とする。 6: $target[i] = $(this).css("display")=="block" ? $(this).next() : $(this).parent().next(); 7: if ($target[i].length) { 8: $(this).click(function(){ // IE においては右側をアニメ起終点にすると固まる事が多いので、 // 強制的に同種の左側起終点に指定を替える。 9: if (jQuery.browser.msie) 10: var k = i==2 && 1 || i==3 && 4 || i==6 && 8 || i; 11: else k=i; // slideToggleEx を起動する。 12: $target[i].slideToggleEx(k, duration, easing, fn); 13: $(this).blur(); // ボタン要素のフォーカスを外す 14: }); 15: } 16: }); 17: } 18: for (var i=0; i<9; i++){ // 0 から 8 迄の巡回処理 // クリックイベントを登録するオブジェクト clk の i プロパティを作成し、 // それに "fireSlideToggleEx-i"( i は 0~8 ) class 名のノードをもつ // jQuery インスタンスを登録する。これにより、ページ内にある // fireSlideToggleEx-n クラス名を持つノードを捉える。 19: clk[i] = $(".fireSlideToggleEx-" + i); // i 番名のノードがなければ loop の先頭行に戻って i++ する。 20: if (!clk[i].length) continue; // i 番名のノードが存在した場合には、当該インスタンスの // 各ノード毎に巡回処理を行うために func 関数を呼び出す。 21: func(i); 22: clk[i].hover( // マウスオーバー/アウト時のボタン背景色の変更 23: function(){$(this).css({backgroundColor:"pink"})}, 24: function(){$(this).css({backgroundColor:""})} 25: ) 26: // マウスが押し下げられた場合の色替え 27: .mousedown(function(){$(this).css('background-color','palegreen')}) 28: } 29:});
非常に苦労したのは、slideToggleEx プラグインの再起動の際に、従前値を保持させる措置でした。
無名関数のトップレベルで、slideToggleEx メソッド外に変数を用意すれば解決することなのですが、是が非でもプラグインのメソッド外に変数を置かないでまとめたかったので、拘ってコードを作りました。
その結果、メソッド外となる点では変わらないのですが、『Javascript 第 5 版』p.144 や cycle プラグインを参考にして slideToggleEx メソッドのプロパティに値を保持させることにしました。
この点は極めて重要なポイントなので、じっくりと記述することにします。
メソッド実行が終わるとその中で定義された変数は消失してしまうので、当該メソッドの起動回数を記憶させる変数は当該メソッド外になければなりません。しかし外に変数を置くのは美しくありません。
そこでメソッドのプロパティを記憶装置にすることにしました。
そのことについて具体的にコードに触れながら、以下で説明したいと思います。
1: var $target=this;
2: var o = $.extend({},$.fn.slideToggleEx.opts);
3: // 初めてそのインスタンスから呼ばれたか、インスタンスが変わった場合のみ
4: if(!o.target || o.target[0]!==$target[0]) {
5: $.extend(o,{
6: orig : o,
7: target : $target,
8: oH : $target.outerHeight() + parseInt($target.css("margin-top"))
9: + parseInt($target.css("margin-bottom")),
10: oW : $target.outerWidth() + parseInt($target.css("margin-left"))
11: + parseInt($target.css("margin-right")),
12: odd : false
13: });
14: $.fn.slideToggleEx.opts = $.extend($.fn.slideToggleEx.opts,o);
15: if ($target.css("position") ==="static")
16: $target.css({position:"relative",top:"0",left:"0"});
17: }
18: o.odd = $.fn.slideToggleEx.opts.odd = !$.fn.slideToggleEx.opts.odd;
上の 18 行のコードは slideToggleEx プラグイン( position 指定版 )の最初の部分から抽出したものです。
これはインスタンスへの参照が必要な場合にその都度 this を使うと、シーンによっては参照先が変わってしまうことがあるので、何らかの変数が必要だからです。別に self でも良いのですが、cycle プラグインに倣って jQuery インスタンスへの参照であることが視覚的にわかるように $ を使い、また target とすることによりアニメ対象であることを意味的にも表示するようにしました。
この行は極めて重要な役割を果たします。これで o オブジェクトに初期値が設定されると共に、同じインスタンスからの 2 度目以降の slideToggleEx の呼び出しの際には、初回呼び出し時に設定された諸値が、初回呼び出し時の 14 行の実行によって、opts プロパティに登録されているので、情報満載のその opts が o に複写されます。
この結果、4 行目の if 文において、o.target プロパティは存在するし、o.target[0] === $target[0] となるので、4 ~ 17 行がパスされます。こうして同じインスタンスに対して、諸値設定を繰り返させない効率化を実現しました。
まず、opts オブジェクトの初期値をバックアップするために、それが転写された o オブジェクトを orig プロパティに保存します(#6)。このバックアップは当面使いませんが、何らかの必要が後で生じるかもしれないとの老婆心です。
次に、target プロパティに $target への参照を登録します(#7)。これにより上で述べたような 二度目以降の呼び出し時の if 文不成立を成り立たせます。
こうすることにより slideToggleEx 対象要素を作成する時には一々 position 指定をしなくても良いようにしました。なおこの処置も当然 1 回だけ行えばよいので、if 文の中で行います。
12 行で false とされた o.odd 値は 14 行によって opts.odd = false と複写されます。そして 18 行でこれを否定することにより opt.odd は trueとなり、これが o.odd にも代入されます。
こうして初回起動時の o.odd 値は true となり奇数回目であることが登録されます。二度目の呼び出し時には 2 行からいきなり 18 行に飛ぶので 18行の o.odd は false、つまり偶数回目となります。
ここにおいて、常に opts.odd に偶数/奇数回数を示す値が保持され続け、そこから o.odd 値が複写され、呼び出しの都度変更されることがポイントです。
この時には if 文の 2 番目の条件が効果を発揮します。起動元インスタンスが変われば、2 行目によってゲットされる o.target はそれ以前の別のインスタンスへの参照を保持しているため、必ず o.target[0] は $.target[0] と異なります。こうして if 文が成立し、改めて起動元インスタンスの要素サイズ( o.oH と o.oW )が計測されます。
そして、その結果は 14 行で opts オブジェクトに複写され、当該インスタンスからの二度目以降の呼び出し時には改めて o オブジェクトに複写されます。
こうして以前のインスタンスに登録されていた要素のサイズ計測結果は、上書きされて消失し、新しい現在の呼び出し元インスタンスに登録されている要素のサイズが opts オブジェクトに保持され、o オブジェクトに登録されます。
このコードはプラグインメソッドではありませんが、容易に slideToggleEx プラグインを利用するために不可欠なものです。そして作成にかなり苦労を重ねた難産コードでした。そこで一節を設けて詳細に説明しておきたいと思います。
■ クリックイベントと slideToggleEx 適用対象を登録するためのコード ( 再掲です ) /* regist click Event to fire slideToggleEx() for nextBlock */ 1:$(function(){ // 当然 DOM が Ready されてから起動する必要があります。 2: var duration = 1000, easing = null,fn = function(){}, clk = {}; 3: function func(i){ 4: $.each(clk[i],function(){ // target オブジェクトは slideToggleEx 対象の個々のノード 1 つだけを格納する。 5; var $target={}; // $target オブジェクトの j プロパティに slideToggleEx 対象ノードを格納する。 // fireSlideToggleEx-x クラス指定されたボタン要素がブロック要素の場合には // next siblingを block 以外の inline 要素の場合には親要素の // next sibling 要素を slideToggleEx 対象とする。 6: $target[i] = $(this).css("display")=="block" ? $(this).next() : $(this).parent().next(); 7: if ($target[i].length) { 8: $(this).click(function(){ // IE においては右側をアニメ起終点にすると固まる事が多いので、 // 強制的に同種の左側起終点に指定を替える。 9: if (jQuery.browser.msie) 10: var k = i==2 && 1 || i==3 && 4 || i==6 && 8 || i; 11: else k=i; // slideToggleEx を起動する。 12: $target[i].slideToggleEx(k, duration, easing, fn); 13: $(this).blur(); // ボタン要素のフォーカスを外す 14: }); 15: } 16: }); 17: } 18: for (var i=0; i<9; i++){ // 0 から 8 迄の巡回処理 // クリックイベントを登録するオブジェクト clk の i プロパティを作成し、 // それに "fireSlideToggleEx-i"( i は 0~8 ) class 名のノードをもつ // jQuery インスタンスを登録する。これにより、ページ内にある // fireSlideToggleEx-n クラス名を持つノードを捉える。 19: clk[i] = $(".fireSlideToggleEx-" + i); // i 番名のノードがなければ loop の先頭行に戻って i++ する。 20: if (!clk[i].length) continue; // i 番名のノードが存在した場合には、当該インスタンスの // 各ノード毎に巡回処理を行うために func 関数を呼び出す。 // ここに func 関数の第 1 引数 clk[i] が外側の関数内にある変数 clk への // 参照を保持しているので、func 関数はクロージャーとなっている。しかし、 // グローバル環境から func 関数への参照はないので、クロージャーを通じた // ローカル変数の活用は行っていない。 21: func(i); 22: clk[i].hover( // マウスオーバー/アウト時のボタン背景色の変更 23: function(){$(this).css({backgroundColor:"pink"})}, 24: function(){$(this).css({backgroundColor:""})} 25: ) 26: // マウスが押し下げられた場合の色替え 27: .mousedown(function(){$(this).css('background-color','palegreen')}) 28: } 29:});
要点は以下の通りです。
以上の要点を踏まえたコードは結構複雑な構成になりました。
そこで IE に限って右側の起終点を利用せずに、同等の左側起終点となるように変換します。
最近では jquery.js を多用し、基本的にエントリイ末尾に jquery.js を利用したコードを記載しています。しかし、それとは別に、このブログを開設した当初から一貫して自作 js ファイルをインクルードし、このブログの基本構造を構築しています。そしてその自作 js ファイルは開設当初こそ頻繁に改訂していましたが、ここ数年は年に 1、2 回程度しか改訂することはありません。
そして昨日、jquery.js を使ったある機能を自作 js ファイルに取込んで上書き upload した時に「事件」が起きたのです。
上書き保存した js ファイルが全く include されないのです。
最初は何が起こったのかさえ分からず、Firebug に表示されるエラー数が余りに多いので吃驚仰天するばかり。早速原因究明に必死になりました。
ブログ閲覧機能の多くを自作 js ファイルに依存しているので、そのファイルがインクルードできないと、このブログは殆ど無価値になってしまうため、必死でした。
小一時間の格闘の末、自作 js ファイルの URI が勝手に変更されていることが発見出来た時には、原因が特定できほっと一息ついたのですが、その後に勝手にアドレスが変わってしまう仕様に疑問を覚えました。
アップロードしたファイルを上書き修正するケースは決して少なくないはずです。
どのようなケースの時にアドレスが自動的に変更されてしまうのか知る由もありませんが、せめてアドレスが変わったことが一目で分かるような警告表示を出すべきだと思います。
確かにアドレスは赤字で表示されますが、まさかその一部が変更になっているとは普通は思いもよりません。ですから単にアドレスを表示するだけでなく、変わった場合には変わったことを警告すべきです。
因みに今回自動的に変更されたアドレスの箇所は以下の通りです。
src="http://blog-imgs-15.fc2.com/・・・"
上の緑色の数字が 31 から 15 へと勝手に変えられてしまったのです。
このようなケースが多発するのかどうか分かりませんが、情報をお持ちの方がいればご教示願いたいと思います。
解読とは大袈裟すぎるかもしれませんが、一見複雑に見える easing 関数式ですが、実は中学生程度の関数の知識があれば十分理解できるものであることを、恥ずかしながら最近納得したばかりなのです。
そこで、easing 関数が実は簡単な代数式であることを改めて整理しておこうと思います。それも中学校で倣う関数の形式に、表現し直してみます。
対象とする easing 関数は jquery.js で定義されている linear 及び swing の他、George Smith 氏による 30 種類の easing 関数です。
大前提として、jquery.js における正規化を踏まえるので、easing 代数式の文字の意味は次のようになります。
t: current time :これは経過時間ですが、jquery.js においては、t を d で割った経過時間推移率とでも呼ぶべき値が t に代入され、それがグラフの横軸変数値となります。
b: begInnIng value : 初期値は jquery.js では常に 0 です。b = 0
c: change In value : 変動値は jquery.js では常に 1 です。c = 1
d: duration : 継続時間ですが、関数式の中には直接登場することは殆どありません。
x → 1-x, y → 1 - y へと座標変換を施すことによって、easeIn 関数から自動的・反射的に easeOut 関数が導き出せます。
この関数は、経過時間推移率が 0.5 以下の場合には easeIn 関数を、それ以上の時には easeOut 関数を適用するだけのことです。
つまり、easeIn 関数が分かれば、座標変換と場合分けによって、簡単に easeOut 関数とeaseInOut 関数が出来てしまうので、easeIn 関数だけ<解読>すれば必要にして十分です。
以下の式において、x は時間推移率( t/d )を、y は easing 関数値を表しています。b,c,t,d は easing 関数で定められている通りの定義です。上に述べた事を踏まえて b = 0, c = 1, t/=d → x としました。
関数名 | Javascript関数式 | 見慣れた関数式 |
---|---|---|
linear | b + c * t | y = x |
swing | ((-Math.cos(t * Math.PI)/2) + 0.5) * c + b | y = -( cos(x * π) ) / 2 + 0.5 |
easeInQuad | c*(t/=d)*t + b | y = x 2 |
easeInCubic | c*(t/=d)*t*t + b | y = x 3 |
easeInQuart | c*(t/=d)*t*t*t + b | y = x 4 |
easeInQuint | c*(t/=d)*t*t*t*t + b | y = x 5 |
easeInSine | -c * Math.cos(t/d * (Math.PI/2)) + c + b | y = - cos(x * π/2) + 1 |
easeInExpo | (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b | y = 2 10 * ( x-1 ), x が 0 の時は y = 0 |
easeInCirq | -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b | y = - (1 - x * x)0.5 + 1 |
easeInBack | if (s == undefined) s = 1.70158; return c*(t/=d)*t*((s+1)*t - s) + b; | y = x * x * ( 2.70158 * x - 1.70158 ) |
easeOutBounce | ((t/=d) < (1/2.75)) c*(7.5625*t*t) + b; (t < (2/2.75)) c*(7.5625*(t-=(1.5/2.75))*t + .75) + b; (t < (2.5/2.75)) c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b; else c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b; | ・x < 1/2.75 → 7.5625* x * x ・x < 2/2.75 → 7.5625* (x-1.5/2.75) * (x-1.5/2.75) +0.75 ・x < 2.5/2.75 → 7.5625* (x-2.25/2.75) * (x-2.25/2.75) +0.9375 ・x >= 2.5/2.75 → 7.5625* (x-2.625/2.75) * (x-2.625/2.75) +0.984375 |
easeInElastic |
var s=1.70158;var p=0;var a=c; if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; if (a < Math.abs(c)) { a=c; var s=p/4; } else var s = p/(2*Math.PI) * Math.asin (c/a); return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; | 作成中 |
90%近いシェアを握っているインターネットエクスプローラの描画エンジンを利用したタブbrowser。沢山のタブbrowserがあるが、多機能、カスタマイズフリー、スクリプト利用等で一日の長がある。Gekkoエンジンへの対応も行われ、IEからの自立独立の方向に向かっている。2005年7月にはIE7が登場する見通しの中で、今後の発展が望まれる。
多様なCSS作成支援機能を備えた、タグ入力式 HTML&CSS作成支援エディタ。スキンデザインもすっきりしている。テキストエディター上で作成するよりも確実で安全にタグ打ちが出来る。
文字コードを選べないのが欠点。
StyleNote同様のタグ入力式 HTML&CSS 作成支援エディタ。長年使用してきたが現在StyleNoteに乗り換えつつある。
クリップボード履歴情報を活用する為のソフト。画像まで履歴を取ってくれるのが嬉しい。このソフトを使わない日は絶対ない程に重宝し、愛用している。
起動中のウィンドウの「コピーできない」説明文などの文字列を取得し、コピー可能な文字データにするツール。何かと便利。
ストリーミングデータを保存することが出来るソフト。動画利用には不可欠なソフトだ。
無料ながらレイヤー機能を有し、スクリプトによる拡張も可能な、sleipnir作者が提供している優れもの画像編集ソフト。
画面キャプチャソフトと言えばこれに勝るものなし、ではないだろうか? 様々な取得方法を有しており、ブログ作成にもHomepage作成に不可欠だ。Jtrimと並んでWoodyBellsの作品。
複数ファイルの同時編集は出来ないが、透過pngも作れる画像編集ソフト。
(以下当該サイトから抜粋)初心者にも簡単に操作が出来るフォトレタッチソフトです。多くの加工機能で画像に様々な効果を与えることができます。非常に軽快に動作するため、ストレスなく操作できます。
Animation Gifファイルを作れる無料ソフト。
キャプチャソフト。画面内にサイト全体が表示しきれない場合でも、これを使えば全体をキャプチャすることが出来る。
画像処理。画像のフォーマット変換のみならず、色数やサイズ、圧縮率の変更まで一括処理できてしまう『BatchGOO!』は、大量の画像をまとめて処理したいときに大変便利なソフト。BMP, TIFF, JPEG, PCX, PNG の相互変換をはじめ、色数・サイズ・解像度の統一、JPEG圧縮率の調節など、ホームページ用の画像や携帯電話用の壁紙を揃えるのに抜群の相性を見せる。(Vectorの当該ソフト紹介頁より抜粋引用)
名前から直ぐに想像が付くように画像のサイズを測るためのソフトだ。Homepage作成には欠かせない。2カラム、3カラムのレイアウトを行う場合に大変重宝する。
ランチャーソフトは沢山あるが、中でもこれが一押しだ。2年以上使ってきたがその操作性には毎日満足している。これを使い始めてからデスクトップには一切のアイコンを表示することをやめてしまった。
AdobeReader7によって、起動時間が長すぎるという長年のユーザーの不満はある程度解消した。そのためこの高速化ソフトは存在価値が低下してしまったかもしれない。AdobeReader6迄はこのソフトによる起動高速化で恩恵を受けてきた。
IE専用が難点だが、様々なサイト内でIDやパスワードを入力するのに重宝するソフト。コンテキストメニューから簡単に起動できるのがGood! sleipnir等のIEの描画エンジンを利用しているブラウザでも使える。
利用しているパソコンの諸元値を取得するには、このソフトがベストだ。インストール済みソフトの一覧が取得できるのも嬉しい。
WMPは機能が豊富なだけ重い。RealPlayerも同様だ。そこでMedia Player Classicを使いたい。動作が軽快なだけではなく、対応しているファイル形式もすこぶる多く、これひとつで、wmvもrmも表示できてしまうのだから凄い! 数多あるMedia Playerの王様と言えるだろう。
自宅でPCを起動しているときには必ず起動しているメディアプレーヤー。何かと過剰なWinampよりも、起動も速くスキンはシンプルだ。
DivX, Xvid, Mov, Vob, Mpeg, Mpeg4, avi, wmv, dv, などの動画をDVD-Video形式に変換できるフリーソフト。クリックするとDVD関連ソフト紹介サイト=「DVDなToolたち」なるHomepageが開きます。