07 | 2009/08 |  09

  1. 無料サーバー

User forum-FC2BLOG-Info-Edit Template-Post-Edit-Upload-LogOut

CSSやJavascript自習の苦闘史を綴っていきたい。恐縮ですがJavascriptを有効にしてご覧ください。
2005年12月から社会問題も掲載!


jquery.js のアニメーションコードの活用 ( 2 ) アニメを使ってグラフを動的に描く

アニメーションを使ってグラフを描く

アニメーション活用エントリイの 2 つ目は、アニメーションを活用してグラフを書くコンテンツです。

そもそも、easing 関数のグラフ化を考えていたのですが、エクセルなどで作って貼り付けるのは余りにも芸がないと思われ、何とか Web サイト上で動的にグラフが描けないだろうか、と前から思っていました。

そして jquery.js のアニメーション解読を進めていく内に、easing 関数それ自体に興味を持ったことと併せて、アニメーションの options.step メソッドを使えば、アニメ途中の情報を取得できるのでグラフが書ける───入浴中にふと(苦笑)、そのことに気付きました。

構想ニ 1 日間、コーディングに約 2 日を要して( と言っても勤めながらなので、夜と朝だけのマイタイムですが )、何とか具体的な成果に漕ぎ着けました。

以下がその結果ですが、何はともあれ、アニメ継続時間を 2 秒か 4 秒程度にし、適当な easing 関数を選択してから、グラフ作成ボタンをクリックしてみてください。

僅か 3 クリックで、今選択した継続時間を要して青いボックスが左下に閉じられ、その後また同じ継続時間を使って右上方向に開かれます。これで「 グラフの座標 」が完成します。

そしてボックスが開き終わると、数10ミリ~数100ミリ秒後にそのボックス内に、今適用された easing 関数のグラフが描かれます。このときボックス下辺はグラフ横軸となって継続時間の推移割合を示し、、ボックス左辺は縦軸となり easing 関数値を表しています。

なお、グラフの大きさは相対的な問題に過ぎません、以下の例示の場合には、青いボックスの大きさ(つまりグラフ座標)を、レイアウト上の必要性から偶々 350 px 角としました。その結果X軸、Y軸両方向とも、時間推移割合と関数値を 350 倍してグラフを作成しています。

【注意】
IE では関数値が 0 未満または 1 を越える easing 関数の場合、固まってしまうようです。ですから Back、Elastic などは IE ではグラフ描画は出来ませんが、もし固まった場合には、この頁をリロードすれば、Javascript の 「 凝固状態 」 を解除出来ます。

  1. まず選択した easing 関数の効果を、ボックス隠蔽 / 表示の 2 つのアニメーションを通じて確認してください。
  2. ボックスが開き終わると直ぐに、今選択し、ボックスの開閉に利用した easing 関数のグラフが、開き終わったボックス内に描かれます。
アニメ継続時間
適用 easing

▲ToTop

グラフ描画の仕組み

jquery.js における easing 関数の扱い
easing 関数とは

「 easing 」 は速度を増減するときに使うもの、とされています。( イージング (Easing) とは | FLASH関連用語集 | ミツエーリンクス )。つまり easing 関数は動きを加速し、あるいは減速する加速度の役割をもっています。

そして、横軸に時間を、縦軸に変動値を取って「 動き 」をグラフ化した場合において、それが直線であれば等速で動いたことを表し、直線の角度は速度の高低を意味します。そして直線でない曲線部分は、加速度が働いた部分であり、上に凸ならば減速、下に凸ならば加速となります。

さて、easing 関数は Tween クラスの場合も jquery.js の場合も、基本的に次の 4 つの変数により変化する二次元関数です。初期値(b)、変動幅(c)、アニメの継続時間(d)、そして現在までの経過時間(t)です。つまり、中学校時代に倣った馴染みの関数式 y = f (x) になぞらえれば、easing 関数の基本形式は次のようになります。

変動後の値 = f ( 初期値, 変動幅, 経過時間, 継続時間 )

ここにおいて時間と共に変化する変数はただ 1 つ 経過時間であり、後は全て定数値となります。勿論、変動幅を時間の関数にすれば、それも変化することになりますが、その場合であっても変動幅は経過時間の関数であり、結局経過時間が唯一の変数となります。

jquery.js の easing 関数

jquery.js では変動幅( c : change で表される場合が多い )を正規化しています。つまり 0 から始まり 1 で終わるように引数が設定されています(jquery.js ver1.32 #4139)。これにより関数はいわば純化され、事の本質が分かりやすくなっていますし、そのことが easing 関数への理解を深める一助ともなっていると思います。

jquery.js における easing 関数の詳細(plugin 30種類を含む)

jquery.js に含まれている easing 関数は linear と swing の 2 類だけですが、それに30種類を追加するプラグイン ( George Smith 氏による 30 種類の easing 関数 ) があります。

エントリイを改めて、これらの 32 種類の easing 関数の全貌を、昔習った数学の関数式を思い出しながら解明しようと思っています。

▲ToTop

どのように関数値を取得するか

options.step メソッドを使います。

私が今自宅で使用しているパソコンでは、 Firefox でこの頁を開いて計測してみたところ、jquery.js の内部メソッドである e.step メソッドは、1 つのアニメ対象要素の 1 つのアニメ対象プロパティ当たり、1 秒間に約 80 回実行されます。(IE の場合、IE 8 であっても回数はもっと少なくなります。Javascript 実行速度が遅いためです。)

このそれぞれの瞬間毎に、 animate メソッドの引数として指定する options.step メソッドを利用して、( jquery.js の内部で作成される ) e インスタンスに保持されているアニメ諸情報を取得します。

easing 関数値は e.pos プロパティに保持されていますので、これを取りだして配列に格納し、その後その配列の各要素を使って、e.step が間歇的に取得した情報から、取得しなかった中間値を補間する計算を行わせて、空隙部を埋めます。

こうして横軸の全てのピクセル数 350 個に対応する関数値を取得又は推定し、グラフ化します。

グラフは 1 × 1 dot の点を 順に青いボックス内に append することにより描きます。

詳細はコード解説部分で触れます。

options.step メソッドで取得できない座標値の補間方法

「 無視できるほど小さい部分では曲線は直線と見なすことが出来る 」という微分の考え方に拠りました。決して複雑ではなく、つまりは単なる直線補間です(苦笑)。

当然アニメ継続時間を長くすればするほど、stepメソッドの呼び出し回数が増えるので、補間値が減り、step メソッドによる算出値が増え、グラフは正確さを増します。

このことは継続時間を長短変えてグラフを描かせてみると良く分かります。継続時間が短いとグラフは直線部分が増え、長くすると直線が減っていきます。これは計算により算出した補間値の数が減るためです。

この件に関する詳細もコード解説部分で触れます。

コーディングの苦労話

ここで行ったことは、ボックスアニメーションを引き起こしつつ、そのボックス内にそのアニメに使用した easing 関数のグラフを書かせるという複合的なアニメーションです。

それ故に、コーディングではグラフを描かせるタイミングについて、幾つかテストしながら調整しました。辿り着いたのは、options.step メソッドの最中でもなく、完全にアニメが終わってからでもなく、step メソッドによる情報取得が終わった段階で直ぐに補間値算出を始めさせ( この段階ではアニメーション動作は進行中です )、かつその補間値計算が終わったら直ぐにグラフ描画を始めさせる、という併行的なコード進行でした。

補間値計算やグラフのプロット作業がアニメーションの進行に干渉せず、かつアニメ完了後出来るだけ短時間でグラフ描画を行わせる最適な方法を探った結果こうなりました。

次に、2 つのバージョンを作った経緯を書いておきたいと思います。

最初に作成したのは以下に掲げた Ver 1 です。このコードは良く見渡すと、drawGraph メソッドが呼ばれる度毎に、アニメ対象の高さ・幅を計算し、隠蔽/表示用アニメ CSS を作り、duration・easing・complete を算出し、step メソッドを登録しています。これはいかにも無駄です。

そこで二度目以降の drawGraph メソッド呼び出し時には、初回呼び出し時に算出した各値を可能な限り再利用するようなコード進行に変更すべきだと思い、Ver 2 を作りました。

▲ToTop

プラグイン drawGraph() メソッドコード
■プラグイン drawGraph() メソッドコード
// ▼ Ver 2 : コード進行の効率化を考慮した改訂版
 1:(function($){
 2:$.fn.drawGraph = function(d,e,fn){
 3: var $box=this, o={}, cnt=0, allsteps=0,layout=[], dotAry=[], arrgAry=[];
   // メソッド実行後も o オブジェクトのプロパティ値を保持させるために、
   // drawGraph メソッドの opts プロパティを作る。
 4: if (!arguments.callee.opts) arguments.callee.opts={};
   // drawGraph メソッドの opts プロパティがあれば、o オブジェクトに併合する。
   // これにより 2 度目以降の drawGraph 呼び出し時に o オブジェクトに opts が複写される。
   // その opts は、直前の drawGraph メソッド実行時に 6 ~ 19 行で作成された o オブジェクトが
   // 20 行において複写されたものなので、2 度目以降の drawGraph 呼び出し時には、
   // 直前実行時に作成したプロパティが o オブジェクトに格納される。
 5: else $.extend(o, arguments.callee.opts);
   // box プロパティがないか(初回呼び出し時)、登録されているノードが異なる場合
   //(今回の使い方では呼び出し元は常に同一なので、ノードが異なることはないが
   // 一般化するために敢えて後者の条件を書いておく。)
 6: if (!o.box || o.box[0] !== $box[0] ) {
 7:  o.box = $box; // this への参照を登録する
 8:  o.log = $box.next(); // log 表示用ノードへの参照を獲得する。
 9:  o.h = $box.height(); // アニメ対象ノードの高さを測定する。
10:  o.w = $box.width(); // アニメ対象ノードの幅を測定する。
11:  o.hideCSS = {height:"toggle",width:"toggle",top:"+="+o.h+"px"}; // 隠蔽アニメ用CSS
12:  o.showCSS = {height:"toggle",width:"toggle",top:"-="+o.h+"px"}; // 表示アニメ用CSS
13: }
   // duration プロパティがないか、あっても引数と異なる場合にのみ 引数 d を o.duration に登録する。
14: if (!o.duration || o.duration !== d) o.duration = d;
   // easing プロパティがないか、あっても引数と異なる場合にのみ 引数 e を o.easing に登録する。
15: if (!o.easing || o.easing !== e) typeof o.duration ==="object" ? "" : o.easing = e;
   // fn が存在して関数で complete プロパティがないか、complete が fnと異なる場合
   // fn を o.complete に代入し、その他の場合には何もしない関数を代入する。
16: o.complete = typeof o.duration ==="object" ? "" :
17:  fn && $.isFunction(fn) && (!o.complete || o.complete!==fn) ? fn : function(){};
   // stepObj は毎回作成しないと doStep が初期化されない。
18: o.stepObj = typeof o.duration ==="object" ? $.extend({},o.duration,{step:doStep}) :
19:  {duration:o.duration, easing:o.easing, complete:o.complete, step:doStep};
   // 以上迄で作成された o オブジェクトを opts に複写し、2 度目以降の drawGraph
   // メソッド起動時に o オブジェクトの複写元とする。
20: $.extend(arguments.callee.opts, o);
21:
22: function doStep(){ // step メソッドを使ってグラフの横軸値と縦軸値を取得する
23:  var e = arguments[1]; ++allsteps; // e.options.step の第 2 引数である e オブジェクトを代入
24:  if (e.prop!=="height") return; // height プロパティ以外の時には何もしない。
25:  ++cnt ;
26:  layout[cnt]={};
27:  layout[cnt]["top"] = ( 1-e.pos ) * o.h; // グラフの縦軸値( easing 関数値 )
28:  layout[cnt]["left"] = e.state * o.w; // グラフの横軸値( 時間推移率 )
29:  dotAry.push(layout[cnt]); // layout 配列の要素を dptAry 配列に格納する。
    // 時間推移率が 100 %以上になったら直線補間関数を呼び出す。
30:  if (e.state >= 1) arrangeGraph();
31: }
32: function arrangeGraph(){ // 直線補間関数
33:  var len = dotAry.length;
34:  while (len){
     // dotAry の先頭要素を削除して arrgAry 配列に格納
35:   arrgAry.push(dotAry.shift());len--;
     // dotary 配列の次の要素のleft値と、格納されたばかりの dotstack 配列要素の left 値の差をとり
36:   if (!len) break;
37:   var diffLeft = Math.round(dotAry[0].left) - Math.round(arrgAry[arrgAry.length-1].left);
     // 差がなければ同一値と見なして dotary 配列の先頭要素を削除
38:   if (diffLeft == 0) {dotAry.shift();len--;}
     // 差が 1 ならば次の値なので dotAry 配列の先頭要素を削除し、それを arrgAry 配列に追加する
39:   else if (diffLeft == 1) {arrgAry.push(dotAry.shift());len--;}
40:   else {// 差が 2 以上あれば、補完値算出用に、その差で top 値を割って平均値を出す
41:    var avr = (dotAry[0].top - arrgAry[arrgAry.length-1].top) / diffLeft;
42:    for (var j=1, obj={}; j<diffLeft ;j++){
43:     // 差の数-1 回だけ補完値を算出し、arrgAry 配列に追加する。
44:     obj ={
45:      top: arrgAry[arrgAry.length-1].top + avr, 
46:      left: ++arrgAry[arrgAry.length-1].left
47:     };
48:     arrgAry.push (obj);
49:    }
50:   }
51:  }
52:  plotGraph();
53: }
54: function plotGraph(){ // ボックス内にグラフを描く
55:  $box.append("<div id='selectedEasingName' style='position:absolute;top:5px;left:15px;'>"+ o.easing +"</div>");
56:  o.log.html("height prop の step 起動回数 :"+ cnt +"<br />全ての step 起動回数:" + allsteps);
57:  for (var i=0, len = arrgAry.length; i<len; i++){
58:   $("<div class='dot hack' />").css({top:arrgAry[i].top+"px",left:arrgAry[i].left+"px"}).appendTo($box);
59:  }
60: }
61:
62: $box.empty(); o.log.empty(); // 表示されている easing 名と log を消す。
   // 隠蔽及び表示アニメを引き起こす。表示アニメの時にグラフも描く。
63: return $box.animate(o.hideCSS,o.duration,o.easing ).animate(o.showCSS, o.stepObj);
64:};
65:})(jQuery);
// ▲ Ver 2 終わり

// ▼ Ver 1 :サイズ測定と o オブジェクト作成を毎回行っている非効率版
(function($){
$.fn.drawGraph=function(duration,easing,fn){
 var cnt=0, allsteps=0, $box=this, layout=[], dotAry=[], arrgAry=[],
   o = $.extend({},{duration:duration, easing:easing});
 $box.h=$box.height(); $box.w=$box.width(); $log=$box.parent().next();
 var hideCSS =  {width:"toggle",height:"toggle",top:"+="+$box.h+"px"},
   showCSS =  {width:"toggle",height:"toggle",top:"-="+$box.h+"px"};
 o.step = doStep;
 o.complete = function(){$.isFunction(fn) ? fn() :function(){} };

 function doStep(){
  var e = arguments[1]; ++allsteps;
  if (e.prop!=="height") return;
  ++cnt ;
  layout[cnt]={};
  layout[cnt]["top"] = ( 1-e.pos ) * $box.h;
  layout[cnt]["left"] = e.state * $box.w;
  dotAry.push(layout[cnt]);
  if (e.state >= 1) arrangeGraph();
 }

 function arrangeGraph(){
  var len = dotAry.length;
  while (len){
   // dotAryの先頭要素を削除して arrgAry 配列に格納
   arrgAry.push(dotAry.shift());len--;
   // dotary 配列の次の要素の left 値と、格納されたばかりの dotstack 配列要素の left 値の差をとり
   if (!len) break;
   var diffLeft = Math.round(dotAry[0].left) - Math.round(arrgAry[arrgAry.length-1].left);
   // 差がなければ同一値と見なして dotary 配列の先頭要素を削除
   if (diffLeft == 0) {dotAry.shift();len--;}
   // 差が 1 ならば次の値なので dotAry 配列の先頭要素を削除し、それを arrgAry 配列に追加する
   else if (diffLeft == 1) {arrgAry.push(dotAry.shift());len--;}
   else {// 差が 2 以上あれば、補完値算出用に、その差で top 値を割って平均値を出す
    var avr = (dotAry[0].top - arrgAry[arrgAry.length-1].top) / diffLeft;
    for (var j=1, obj={}; j<diffLeft ;j++){
     // 差の数 - 1 回だけ補完値を算出し、arrgAry 配列に追加する。
     obj ={
      top: arrgAry[arrgAry.length-1].top + avr, 
      left: ++arrgAry[arrgAry.length-1].left
     };
     arrgAry.push (obj);
    }
   }
  }
  plotGraph();
 }

 function plotGraph(){
  $box.append("<div id='selectedEasingName' style='position:absolute;top:5px;left:15px;'>"+ o.easing +"</div>");
  $log.html("height prop の step 起動回数 :"+cnt+"<br />全ての step 起動回数:" + allsteps);
  for (var i=0, len = arrgAry.length; i<len; i++){
   $("<div class='dot' />").css({top:arrgAry[i].top+"px",left:arrgAry[i].left+"px"}).appendTo($box);
  }
 }

 $box.empty();$log.empty();
 return $box.animate(hideCSS,duration,easing ).animate(showCSS, o);
};})(jQuery);
// ▲ Ver 1 終わり

// drawGraph プラグインを使って実際にグラフを書かせるためのコード
(function($){
 var selDuration=2000,selEasing="swing";
 // アニメ継続時間コンボから 選択された duration を取得
 $("#sel1_728").change(function(){selDuration = +$(this).val();});
 // easing リストから 選択された easing 関数を取得
 $("#sel2_728").change(function(){selEasing = $(this).val();});
 $("#btn1_728").click(function(){$("#box1_728").drawGraph(selDuration,selEasing);});

 $("input").mouseup(function(){$(this).css({backgroundColor:""}).blur();})
  .hover(function(){$(this).css({backgroundColor:"pink"});}
  ,function(){
    $(this).css({backgroundColor:""});
  })
  .mousedown(function(){$(this).css({backgroundColor:"lightgreen"})});
})(jQuery);

▲ToTop

drawGraph() メソッド Ver 2 に関する補足説明

直前起動時に作成したアニメ対象に関する諸情報を出来る限り 2 度目以降に活用する───これが Ver 2 作成の動機であり目的です。『 Javascript 第 5 版 』p.144 を参考にして、メソッドのプロパティを活用する方法を作ったまでは良かったのですが、なかなか順調に動きませんでした。18 行目の stepObj も他のプロパティと同様に初回作成時の値を使うコードを書いたのですが、これが躓きの原因でした。

14~17 の 4 行では、o.duration、o.easing、o.complete の各プロパティの存在を確認し、それがあれば何もしないようなコードを書きました。こうしてコード進行の効率化を図ったのでした。

そしてそれ自体は問題なく動いたので、安心して 18 行も if (o.stepObj) 文を書いてしまったのです。

ところが、こうすると 2 度目以降においては o.stepObj は再定義されないので、初回時の値をそのまま保持し続けてしまいます。その結果 3 行目の cnt = 0 が 2 度目以降に読み込まれ初期化されても、その後の過程で opts プロパティに o オブジェクトを複写・待避させているため、o.stepObj 内の cnt は初期化されず累積されてしまうのです。

そのことが分かるまでに数時間を要したのですが、大きな落とし穴に落ち込んでしまった結果、貴重な経験を重ねることが出来ました。

▲ToTop

jquery.js のアニメーションコードの活用 ( 1 ) slideToggleEx メソッドの作成

アニメコードの活用とは

これまで 9 回に亘ってエントリイしてきたアニメーションコードの解読を踏まえて、今度はアニメーションコードを活用した、実例を掲載してみようと思います。

作成した(及び作成予定の)実例
  1. slideToggleEx() プラグインメソッド

    こちらは苦労の末やっと作成を完了し、このエントリイの下の方にその実例とコードを掲載しました。

    このプラグインメソッドは、端的に言って slideToggle() メソッドの拡張版です。

    jquery.js に収められている slideToggle() は slideDown() と slideUp() メソッドを交互に実行する切り替えメソッドで、ボックス領域の表示と隠蔽を上辺を基点として切り替える作用を起こします。

    今回作成した slideToggleEx() メソッドは、slideToggle() が表示/隠蔽を上辺だけから行うのに対して、以下のように様々な箇所から表示され、隠蔽されるように、大幅に機能拡張したものです。

    slideToggleEx() メソッドは、ボックスの (0) 中心、(1) 左上、(2) 右上、(3) 右下、(4) 左下、(5) 上辺、(6) 右辺、(7) 下辺及び (8) 左辺の、全部で 9 つのアニメ起終点に対応させた、ボックスの縮小隠蔽 / 展開表示を行うアニメーションです。

    起終点は簡単に引数だけで指定出来るように工夫し、更に、同一ボタンのクリックによる、或る起終点からの隠蔽/表示アニメーションだけでなく、様々な起終点からのアニメを、連続して起動しながら、隠蔽/表示を繰り返す連続アニメーションにも対応させた、まさしく機能拡張版です。

  2. easing 関数の値をグラフ化するアニメーションを作成します。

    これはボックスのサイズを大きくするアニメーションを行なわせながら step メソッドを活用し、その時に使用している easing 関数の値を、当該ボックスの中にグラフとして示すものです。グラフをアニメーション機能を使って描くという試みにチャレンジしてみます。

    実は次のエントリイにそのグラフ描画アニメーションを掲載しました。

  3. ボックスだけでなく画像も同時に animate するメソッド

    こちらはまだ未着手です。cycle アニメーションのように、画像も一緒に拡大縮小するアニメーションを作る予定です。

    なおこのメソッドは、これまでのように表示されているコンテンツをアニメートするのではなく、非表示あるいは存在していなかったものをアニメートで出現させるタイプになるはずです。

  4. その他これから考えますが、余り複雑にしても意味はないでしょう。組み合わせれば済むことですから。

▲ToTop

完成した slideToggleEx() メソッドについて

何はともあれ、結果をお披露目します。

当然のことですが、 slideToggleEx() メソッドはプラグイン形式、つまり、jQuery プロトタイプオブジェクトとして作成しました。こうすれば使い勝手がより一層汎用的になるからです。

 
 
18 個のアニメーションを slideToggleEx() で一気に履行する

上の 「 slideToggleEx() 一気履行 」 ボタンをクリックすると、「 ボックスを終点に隠蔽し、そこを始点として展開する 18 の連続アニメーション 」 が始まります。

アニメーションは、ボックスの (0) 中心、(1) 左上、(2) 右上、(3) 右下、(4) 左下、(5) 上辺、(6) 右辺、(7) 下辺及び (8) 左辺の 9 つの点または辺をこの順番に起終点/線とし、 閉じて開く 2 回 × 9 種類、つまり 18 回連続して起動されます。このアニメではそれぞれのアニメ継続時間は全て 1 秒とし、easing 関数は全て同一の、穏やかな swing を使いました。

なお、この連続起動アニメも、上で説明する 3 つの個別起動アニメも全て、上の薄黄色地ボックスの中にある、 青地金色ボーダーのボックスを対象として作動します。また動くボックスの元の位置が分かるように、アニメ対象ボックスを包み込む不動の背景ボックス(背景色ブラウン)を配置してあります。これにより margin を含めてアニメ対象になっていることが視覚的にも確認できるはずです。

slideToggleEx() を単独で使ってみる。

(1) 起終点がボックス中心、(2) 起終線が左辺、(3) 起終点が右下隅 ── の 3 つを上に用意しました。

これらはそれぞれに単独で作動し、1 回目のボタンクリックで隠蔽アニメが、同一ボタンの2 回目のクリックで表示アニメが、それぞれ起動します。アニメ継続時間は全て 2 秒に設定しました。

easing 関数と IE

ここで easing 関数と IE の関係について一言付言しておきます。上の単独起動のアニメーションには全て IE で動くことを確認した、それぞれ異なる easing 関数を使用しています。

値が 0 を下回るか、1 を越えるような easing 関数を指定すると、Firefox では問題なく動くのですが、IE の場合固まってしまいます。使っている easing 関数セットは、最も有名と思われるオーソドックスな jQuery Easing Plugin (version 1.3) なので、関数指定に問題があるとは思えません。

どうして IE では動かないのか解明出来ず、このために上のサンプルでは、関数値が 0 ~ 1 に収まるような easing 関数を選んで指定しました。

▲ToTop

プラグイン slideToggleEx() の仕様

■ slideToggleEx() の仕様

 書式: slideToggleEx ( type, duration, easing, function )

 type: 0-中心、1-左上端、2-右上端、3-右下端、4-左下端、
     5-上辺、6-右辺、7-下辺、8-左辺
      ─── この数値でアニメ起終点位置を指定します。
 duration; アニメ継続時間(ミリ秒単位、または "slow","fast"。
       無指定の場合 jquery.js の仕様により 400 ミリ秒となります。)
       なお、オブジェクト形式でこの第 2 引数を指定することも可能です。
 easing: easing 関数(文字列で指定します。無指定の場合 
            jquery.js の仕様により swing が適用されます)
 function: アニメ終了後に起動する関数を指定します(不要ならば無指定)
 
 ※ 第 2 引数をオブジェクト形式で指定する場合には、第 3、第 4 引数は指定しません。

slideToggleEx() の仕様上の特徴

アニメ位置指定について

アニメの位置指定を絶対指定にするか、相対指定とするか、最初は悩みました。そしてその是非を判断するために、両方を作成した上で、ここには相対指定版を掲載しました。相対指定の方がコードをぱっと見て分かりやすいと判断したためです。

アニメの位置指定を絶対指定方式で行う場合には、アニメ対象をラップするボックスが位置指定の基準座標となるため、指定上の容易性を考慮するとアニメボックスのマージン辺と同じサイズの 「 基準ボックス 」 を作らねばなりません。

アニメ位置を相対位置で指定すれば、このような基準座標ボックスの必要性はないので、その意味からも相対指定方式の方が利便性がよいと思われます。

アニメ対象の position 指定について

当初はアニメ対象の CSS に position:relative を指定していましたが、slideToggleEx プラグインの運用を重ねるに連れ、その都度指定することが面倒であることに気がつきました。

slideToggleEx プラグインメソッドは top と left をアニメ対象としていますので、アニメ対象には static 以外の position 指定が為されていなければなりません。しかし、それを一々対象要素に指定するのは結構面倒です。

このため、仮にアニメ対象に position 指定が為されていない場合でも相対指定となるよう、Javascript コードによって強制的に指定することにしました。

outerWidth、outerHeight の true 指定について

jquery.js では outerWidth(true)、outerHeight(true) と指定すると margin 辺迄のボックスサイズを計測する仕様になっています。(#4342 ~ 4346)

ところが、非圧縮版の jquery.js においてはその通り機能するのですが、packed 版ではそのコードをチェックしても間違ってはいないと思われるのですが、巧く作動せずボーダー辺迄の採寸しかしてくれませんでした。

ここでは margin 辺までの外寸が欲しかったのでやむを得ず、outerWidth、outerHeight には引数 true を付けずに、ボーダー辺までのサイズを取得しておき、それに margin サイズを追加して margin 辺までの採寸を行わざるを得ませんでした。

▲ToTop

以下のコード等の表示/隠蔽ボタンには slideToggleEx を利用した

コードなどを表示したり隠蔽したりするボタンは、従来から slideToggle メソッドを仕込んでいました。

そして今回、当然ですが、そのボタンには slideToggle ではなく slideToggleEx を仕込みました。

早速実用に供したわけですが、使う上での注意点を列挙しておきます。

実用上の注意点
  1. 表示/隠蔽対象となる要素タグスクロールバーがある場合

    表示/隠蔽対象となる要素タグが、縦または横にスクロールバーがある場合、その要素を隠蔽後に表示させた場合、スクロールバーの幅や高さ分だけ、左にインデントが掛ってしまったり、下が空いてしまったりします。

    これは jquery.js における アニメ時の overflow プロパティの扱いに起因すると思われますが、確定的なことは調査し切れていません。

    従って、特に縦スクロールバーがある要素を slideToggleEx 対象とする場合には、ボックスの右の 2 つの端部や右辺を起終点/線にしないようにするか、あるいはボックス幅を明示的に指定することが賢明です。

  2. IE 対策

    IE ではボックスの右側を起終点とするアニメは巧く作動しません。スクリプトが固まってしまいます。

    このため右上端、右辺及び右下端の 3 つの起終点を指定した場合には、IE だけは、左上端、左辺及び左下端が指定されたものと読み替えるよう、コードを改変しました。

上のアニメ実例における slideToggleEx() の指定内容

直下のボタンをクリックすると、上のアニメ実例を起動させる指定内容が表示されますが、このボタンには No 5 の slideToggleEx 、つまり上辺からのスライドダウン/アップを仕込みました。不透明度もアニメ対象にしているので、slideToggle よりもより印象的になっている、と思います。なお、効果を確認するためにも幾つかのアニメは時間を 1 秒掛けるようにしました。

■上で示した実例の slideToggleEx() 指定内容
<script type="text/javascript">//<!--
$(function(){
 // 18 アニメ連続起動
 $.box = $("#box1_727");
 $("#btn1_727").click(function(){
  $box
   .slideToggleEx(0,1000,"swing")
   .slideToggleEx(0,1000,"swing")
   .slideToggleEx(1,1000,"swing")
   .slideToggleEx(1,1000,"swing")
   .slideToggleEx(2,1000,"swing")
   .slideToggleEx(2,1000,"swing")
   .slideToggleEx(3,1000,"swing")
   .slideToggleEx(3,1000,"swing")
   .slideToggleEx(4,1000,"swing")
   .slideToggleEx(4,1000,"swing")
   .slideToggleEx(5,1000,"swing")
   .slideToggleEx(5,1000,"swing")
   .slideToggleEx(6,1000,"swing")
   .slideToggleEx(6,1000,"swing")
   .slideToggleEx(7,1000,"swing")
   .slideToggleEx(7,1000,"swing")
   .slideToggleEx(8,1000,"swing")
   .slideToggleEx(8,1000,"swing",function(){alert("18 の連続アニメが完了しました")});
 });
 // 起終点をボックス中心とするアニメ起動
 $("#btn2_727").click(
  function(){
	 $box.slideToggleEx(0,1000,"easeInOutQuint");
	}
 );

 // 起終線をボックス左辺とするアニメ起動
 $("#btn3_727").click(
  function(){
	 $box.slideToggleEx(8,1000,"easeInOutSine");
	}
 );

 // 起終点をボックス右下端とするアニメ起動
 $("#btn4_727").click(
  function(){
	 $box.slideToggleEx(3,1000,"easeInOutQuart");
	}
 );

 // ボタン
 $("button")
  .mousedown(function(){$(this).css({backgroundColor:"lightgreen"})})
  .mouseup(function(){$(this).blur().css({backgroundColor:""})})
  .hover(function(){
    $(this).css({backgroundColor:"pink"});
   },function(){
    $(this).css({backgroundColor:""});
  })
  .blur();

// regist click Event to fire slideToggleEx() for nextBox
// button の次にあるブロックを slideToggleEx の操作対象とするための一般的コード
 	var duration=1000, easing=null,fn=function(){}, clk={};
	function func(i){
		$.each(clk[i],function(){
			var $target={};
			$target[i] = $(this).css("display")=="block" ? $(this).next() : $(this).parent().next();
			if ($target[i].length) {
				$(this).click(function(){
					if (jQuery.browser.msie)
						var k = i==2 && 1 || i==3 && 4 || i==6 && 8 || i;
					else k = i;
					$target[i].slideToggleEx(k, duration, easing, fn);
					$(this).blur();
				});
			}
		});
	}
	for (var i=0; i<9; i++){
		clk[i] = $(".fireSlideToggleEx-" + i);
	  	if (!clk[i].length) continue;
		func(i);
		clk[i].hover(
			function(){$(this).css({backgroundColor:"pink"})},
			function(){$(this).css({backgroundColor:""})}
		)
		.mousedown(function(){$(this).css('background-color','palegreen')})
	}
});
//--></script>

プラグイン slideToggleEx() に係るスタイスシートと Javascript コード

直下のボタンには No 2 の slideToggleEx 、つまり右上端部からのスライドダウン/アップを仕込みました。最初は縦スクロールバーがないので、右上端部からの起終点も簡単に実行できると思いましたが、幅を明示的に指定して初めて思い通りに作動しました。

但し、またしても IE で障害が出ました。右上端部への隠蔽が起動せず、何故かスクロールバーが消えてしまうだけで他には何も起こりませんでした。仕方なく IE の場合のみ No1 つまり左上端部を起終点とするようにコーディングし直しました。

■ スタイルシート
<style type="text/css"><!--
 #animArea_727 {
  position :relative; z-index:1; margin:1em auto; top:0px;left:0px;font-size:16px;color:black;
  width:650px;height:300px;background-color:cornsilk;border:dotted 2px goldenrod;
 }
 #animArea_727 > div {
  position: absolute; z-index:2; top:50px; left: 170px;
  width:336px; height:186px; background-color:brown;
 }
 #box1_727 { /* 敢えて position 指定は行っていません。*/
  margin:10px 10px 10px 10px;width:300px; height:150px;
  background-color:royalblue; border:ridge 8px palegoldenrod;
 }
 button {overflow:visible;padding:2px;}
--></style>

▲ToTop

直下のボタンには No 7 の slideToggleEx 、つまり下辺からのスライドアップ( 表示する場合 )/ダウン( 隠蔽する場合 )を仕込みました。スライドが下から始まると独特の雰囲気になります。

■プラグイン slideToggleEx() メソッドコード
<script type="text/javascript">//<!--
// top と left をアニメ対象プロパティにする position relative バージョン
(function($){ // jQuery plugin : slideToggleEx() Release at 2009.8.21, verup 2009.9.9
var ver="not absolute but relative animation on 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, // backup しておく
   target : $target, // 起動インスタンスを記録する
   oH : $target.outerHeight() + parseInt($target.css("margin-top"))
    + parseInt($target.css("margin-bottom")), // インスタンスの第一要素の margin 辺迄の高さを測る
   oW : $target.outerWidth() + parseInt($target.css("margin-left"))
    + parseInt($target.css("margin-right")), // インスタンスの第一要素の margin 辺迄の幅を測る
   odd : false
  });
  // opts オブジェクトに上で得た o オブジェクトを併合し、Ex メソッドが終わっても情報を保存する
  $.extend($.fn.slideToggleEx.opts,o);
  // インスタンスの要素の position 指定を確認し、必要な指定を行う。
  if ($target.css("position") ==="static") $target.css({position:"relative",top:"0",left:"0"});
 }
 // 起動回数が偶数か奇数かを記録する。連続起動の際にはこれによって hide メソッドを起動するのか、
 // showメソッドを起動するのかを判断する。
 o.odd = $.fn.slideToggleEx.opts.odd = !$.fn.slideToggleEx.opts.odd;
 o.hidden = $target.is(":hidden"); // 要素が隠れているかどうか判定する
 o.duration = duration || o.duration; // duration が オブジェクトの場合も含めて対応
 // 以下の 2 つは、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
// default 値設定と記憶用プロパティとしての機能のために
$.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);
//--></script>

▲ToTop

jquery.js におけるアニメーションコードの解読 ( 9 )

これまで 8 回に亘って jquery.js のアニメーションコードを解読してきました。自らの理解を深めるために、また閲覧者により関心を抱いて戴くためにも、いくつかのサンプルアニメーションも作成し、その解説もおこなってきました。

そしてついに、このエントリイをもって jquery.js のアニメーションコードの全ての解読を終えることになります。既に高い山は登り終えているので、残されたメソッドは比較的簡単なものばかりです。

jQuery().slideDown() メソッド

引数:speed、callback

返値:jQueryインスタンス

機能:要素の幅を一定に保ちつつ、高さを 0 から徐々に所定の高さまで変化させながら要素を表示します。

第 1 引数に高さに関わる各種の CSS プロパティ値を全て "show" に設定した CSS オブジェクトを、第 2 引数に speed を、第 3 引数に callback を設定して、animate メソッドを起動し、その返値を返します。

このメソッドは具体的には次のように定義されます。
 jQuery.fn.slideDown = function(speed,callback){
  return this.animate({
    height:"show", marginTop:"show", marginBottom:"show",
    paddingTop:"show", paddingBottom:"show"
  },speed,callback)
 } //(#3954~3964より)

slideDown メソッドのコード進行を具体的に跡付けると次のようになります。

  1. 高さに関わる各 CSS プロパティ値を全て "show" に設定した CSS オブジェクトを第 1 引数とすることにより、animate メソッドからそれらの各 CSS プロパティ毎に、当該 CSS プロパティ名称を引数として e.show(prop) メソッドが起動されます。(#3892-3896)
  2. 各プロパティ毎に起動される e.show(prop) メソッドは、プロパティが対象ノードのマージン上下、ボーダー上下、又はパッディング上下値の場合には初期値を 0 に設定し、それが要素高さの場合には 1 に設定します。また e.cur() メソッドを呼び出して、各々の要素属性値、styleオブジェクトのプロパティ値または算出スタイル値を終了値に設定します。こうして初期値と終了値を設定してから、各プロパティ毎に e.custom() メソッドを実行します。
  3. e.show() メソッドから起動された e.custom() メソッドは、e オブジェクトのプロパティに初期値、終了値、現在時刻などを格納してから、当該 CSS プロパティに対して、今度は e.step() メソッドを起動します。
  4. e.custom() メソッドから起動された e.step() メソッドは、その時迄の経過時間が duration 時間を下回っている場合には、指定された easing 関数があればそれを使い、なければ swing 関数を使って、jQuery.easing() メソッドを起動します。
  5. easing メソッドは、経過時間と継続時間を変数とする所定の easing 関数により、0 から 1 の間の数値を返す関数で、この値が得られたらば、次に easing 関数による値と、初期値と終了値の差などからその時点のプロパティ値を算出します。そしてこの当該 CSS プロパティの値を、e.now プロパティに代入します。
  6. こうして当該時点の当該プロパティの CSS 値を取得した後、e.update() メソッドを起動します。
  7. update メソッドは、当該要素に style オブジェクトがあって、かつ当該プロパティ値が null 値ではないときには、当該 style オブジェクトの当該プロパティに、e.now の値を単位付きで代入します。
    他方、当該要素に style オブジェクトがないか、それがあっても当該プロパティ値が null だったら、当該要素の当該属性に e.now の値を代入します。(#4020)
    その後、処理対象プロパティが height で当該要素に style オブジェクトが存在すれば、その display プロパティを block とし、style オブジェクトがなければ何もしません。
  8. こうして高さに係る style オブジェクトの各 CSS 値または要素属性に e.now を与えられた当該要素は、ブラウザによりその e.now 値に従って画面に表示されます。
  9. 以上の 3. ~ 7. までの、一部にクラスメソッドを含む、e インスタンスオブジェクトに属するメソッドの連続実行( e.show → e.custom → e.step → $.easing → e.update ) により、当該要素の当該プロパティの、その時の値を style オブジェクトか要素のタグ属性に代入してから、e.show メソッド実行過程の最後において、show() インスタンスメソッドが引数なしで起動されます。(#4080)
  10. しかし、既に 7. において 要素属性に e.now 値が代入されるか、elem.style.display 値が "block" となっているので、この時点で表示行為は終わっています。
  11. では、何故引数なしで show() インスタンスメソッドを起動する必要があるのでしょうか。
    この疑問を質すにはこのメソッドが、jQuery.data() クラスメソッドを使用して、インスタンスの各要素に elem.style.display プロパティ値を関連づけることを思い出す必要があります。つまり、このメソッドは単に画面に要素を表示するだけではなく、その要素が直前に表示済みか、隠蔽されているかを記録する機能を有しています。ここで show() インスタンスメソッドを起動する目的は、この「履歴情報」を設定し、あるいは確認する事にあります。
  12. 具体的に言えば、ここで起動される show() メソッドにより、display 値が block であることが当該要素への紐付け行為( $.data() メソッドの実行)によって「登録され」、あるいは登録済みであることが確認されます。

▲ToTop

jQuery().sideUp()

引数:speed、callback

返値:jQueryインスタンス

機能:要素の幅を一定に保ちつつ、高さを所定の高さから 0 まで変化させながら要素を隠蔽します。

このメソッドは具体的には次のように定義されます。
 jQuery.fn.slideUp = function(speed,callback){
  return this.animate({
    height:"hide", marginTop:"hide", marginBottom:"hide",
    paddingTop:"hide", paddingBottom:"hide"  },speed,callback)
 } //(#3954~3964より)

このメソッドは、高さに関わる各種の CSS プロパティ値を全て "hide" に設定したCSSオブジェクトを第1引数に、speed を第2引数に、そして callback を第3引数にして animate メソッドを起動し、その返値を返します。言うまでもなく、jQuery().slideDown() メソッドの逆のことを行うメソッドです。

e.hide() を起動することにより、高さに係る各種 CSS スタイルプロパティのカレントスタイル値を初期値をとし、最終値をゼロに設定してアニメーションを行います。

具体的なコード進行は slideDown と酷似しますが、e.show()メソッドよりも、e.hide()メソッドの方が、また show()インスタンスメソッドよりも hide() インスタンスメソッドの方が、共にコード文字数は少なく、単なる対照的な関係ではありません。

  1. 高さに関わる各 CSS プロパティ値を全て "hide" に設定したCSSオブジェクトを第1引数とすることにより、animate メソッドからそれらの各 CSS プロパティ毎に、CSS プロパティ名称を引数として e.hide(prop) メソッドが起動されます。(#3892-3896)
  2. 各プロパティ毎に起動される e.hide(prop) メソッドは、当該要素の style オブジェクトの当該プロパティ値を this.options.orig[this.prop] に代入してから、高さに係る当該 CSS プロパティのカレントスタイル値を初期値に設定し、終了値をゼロに設定して、各プロパティ毎に e.custom() メソッドを実行します。
  3. e.hide() メソッド内で起動された e.custom() メソッドは、アニメーションの各種条件を設定するためのものです。
  4. e オブジェクトに初期値や終了値、現在時刻などをプロパティとして格納してから、当該 CSS プロパティに対して、今度は e.step() メソッドを起動します。
  5. e.custom() メソッドから起動された e.step() メソッドは、custom メソッドで設定された諸条件値を使ってアニメーションを「刻みます」。つまり、時間経過と共に変動する CSS 値を経過時間に即して設定します。
  6. その時迄の経過時間が指定された duration 時間を下回っている場合には、指定された easing 関数があればそれを使って、なければ swing 関数を使って、jQuery.easing() メソッドを起動します。
  7. easing メソッドは、経過時間と継続時間を変数とする所定の easing 関数により、0 から 1 の間の数値を返す関数で、この値と初期値と終了値の差などから、その時点のプロパティ値を算出します。そしてこの当該 CSS プロパティの値を、e.now プロパティに代入します。
  8. こうして当該時点の当該プロパティのCSS値を取得してから、表示を制御する e.update() メソッドを起動します。
  9. update メソッドは、まさに表示の「更新」を行うものです。
  10. 当該要素に style オブジェクトがあって、かつ当該プロパティが null 値ではないときには、当該 style オブジェクトの当該プロパティに、e.now の値を単位付きで代入します。他方、当該要素に style オブジェクトがないか、それがあっても当該プロパティが null だったら、当該要素の当該属性値に e.now の値を代入します。その後、処理対象プロパティが width か height だったら、当該要素の style オブジェクトの display プロパティを block とします。block により、幅や高さは初期値から次第にゼロに近づく e.now 値に応じて、表示サイズが小さくなっていき、最終的にはゼロに到達して当該要素は全く表示されなくなります。
  11. 3 ~ 7 までの、一部にクラスメソッドを含む e オブジェクトに属する一連のメソッド連続実行( e.show → e.custom → e.step → $.easing → e.update )により、当該要素の当該プロパティの、その時の値をstyleオブジェクトか要素のタグ属性に代入するわけです。

▲ToTop

jQuery().slideToggle()

引数:speed、callback

返値:jQueryインスタンス

機能:e.show() 又は e.hide() メソッドを交互に起動するメソッドです。対象としたタグ要素が表示されていれば e.hide()メソッドを起動して隠蔽し、隠蔽されていれば e.show() メソッドを起動して表示します。

jQuery().fadeIn()

引数:speed、callback

返値:jQueryインスタンス

機能:CSS オブジェクト {opacity:"show"} を第1引数に、speed を第2引数に、callback を第3引数にして animate メソッドを起動し、その返値を返します。これにより要素が透明から次第に不透明になっていって、表示されるアニメが実現されます。

このメソッドは具体的には次のように定義されます。
 jQuery.fn.fadeIn = function(speed,callback){
  return this.animate({opacity:"show"},speed,callback)
 }
  1. 第1引数により、animate メソッド内から e.show()メソッドが起動され、opacityスタイル値の初期値を 0 に、終了値を現在値に設定して、e.custom メソッドを作動させます。
  2. e.custom メソッド→ e.step メソッド→ e.update メソッドの連鎖起動を通じて、不透明度アニメーションの初期値などの設定とその時点における値設定を行ってから、e.update メソッド内から e インスタンスを引数にして、jQuery.fx.step[opacity] クラスプロパティメソッドが起動されます。
  3. jQuery.fx.step.opacity メソッドにより、当該要素の不透明度が step メソッド内で設定された e.now 値に設定されます。

jQuery().fadeOut()

引数:speed、callback

返値:jQueryインスタンス

機能:fadeIn メソッドとの違いは animate メソッド第 1 引数の opacity プロパティ値が hide になることだけです。これにより要素が不透明から次第に透明化していき最後には完全透明になって、表示が消えます。

このメソッドは具体的には次のように定義されます。
 jQuery.fn.fadeIn = function(speed,callback){
  return this.animate({opacity:"hide"},speed,callback)
 }

▲ToTop

jQuery().stop(clearQueue,gotoEnd) メソッド

引数:2 つの引数は共に boolean 型。最初の引数を true に設定すると、アニメ待ち行列配列を空にすることによって、次以降に登録されているアニメを全て登録から削除します。2 番目の引数を true に指定すると、全てのアニメーションの全ての step メソッドを格納している jQuey.timers 配列から、今まさにアニメを停止しようとしているタグ要素に関わる step メソッドを特定し、当該要素の当該アニメを、その step メソッドを最後に終了させます。

返値:jQuery インスタンス

機能:このメソッドは登録されているアニメーションを削除したり、進行中のアニメーションを停止させます。

引数に何も指定しないと、単に現在動いているアニメーションを停止させます。同じ要素に登録されている次のアニメーションは停止されませんし、停止指示の前の時点で動き出していて未だ終わっていない e.step メソッドも停止されません。

3927: stop: function(clearQueue, gotoEnd){
3928:  var timers = jQuery.timers;
3929:
3930:  if (clearQueue) // clearQueue が true ならば
3931:   this.queue([]); // 空配列を queue メソッドの引数として、待ち行列を空にし
3932:   // jQuery インスタンスに登録されているアニメーションを削除する。
3933:  this.each(function(){ // インスタンスの要素毎に巡回処理
3934:   // go in reverse order so anything added to the queue during the loop is ignored
3935:   for ( var i = timers.length - 1; i >= 0; i-- )
       // timers 配列の要素は custom メソッド内の #4046 行で定義されている
       // t 関数であり、これは e.step メソッドを起動させアニメを刻む役割を果たす。
       // この関数には animate メソッドが起動された全てのタグ要素の、全てのアニメーションの
       // 各 CSS プロパティ毎の e.step メソッドが登録されている。
       // それを逆順に巡回走査して、それが現在対象としている要素のメソッドだったら
3936:    if ( timers[i].elem == this ) {
3937:     if (gotoEnd) // gotoEnd が true に指定されていれば
3938:      // force the next step to be the last
3939:      timers[i](true); // 次に起動する e.step メソッドを最後にそのアニメを終わる。
3940:     timers.splice(i, 1); // timers 配列の i 番目を削除。実行中の e.step メソッドを削除する。
3941:    }
3942:  }); // 巡回処理終わり
3943:
3944:  // start the next in the queue if the last step wasn't forced
     // gotoEnd が 未定義か false ならば当該要素に登録されている次のアニメ関数を実行する。
3945:  if (!gotoEnd)
3946:   this.dequeue();
3947:
3948:  return this;
3949: }

jQuery.easing() クラスメソッド

引数:easing 関数名を文字列形式で指定します

返値:easing 関数による産出値

機能:初期値、アニメ継続時間、経過時間などから計算が行われますが、単純に言えば、様々な代数式に、経過時間割合( この割合のことを「 動作ステータス 」というらしい )を変数として代入して値が算出されます。

留意すべき点は、経過した時間そのものではなく、そのアニメ継続時間に対するその割合が変数となることでしょうか。

なお以前にも紹介しましたが、 easing 関数の極めて分かりやすい例示として、以下のサイトが非常に参考になります。「その Easing の各々の動きの違いを示したサイトとして興味深いのは、flash ムービーですが、 easing_demo が実に分かりやすく有益です。これによりそれぞれの Easing がどんな挙動をするのか一目瞭然に理解できます。easing 関数内の代数式がグラフ化されていることも非常に有益です。」( jQuery プラグインとしてアニメーションポップアップ : animatedPopup を自作した。 )

jquery.js におけるアニメーションコードの解読 ( 8 )

アニメーションの進行途中の情報を見るサンプル

このエントリイでは前エントリイに続いてサンプルを掲載します。今回のサンプルでは Web サイトでは余り見かけない「 アニメ-ションが進行しているその途中の情報 」を取り上げます。

これまでの 「 jquery.js におけるアニメーションコードの解読 (1) ~ (6) 」 からも分かるとおり、アニメーションが実現されるコードの進行過程は大変複雑で、様々なプロパティとメソッドが駆使されて 「 動き 」 が演出されています。

そしてアニメを扱ったサイトの多くは、アニメそのもの多様性・独自性・ユニークさを競い合っていても、アニメ-ション進行過程における情報を可視化したものは余り見受けません。

そこで、実際にアニメーションを起こしながら、その進行過程における諸情報を取りだすサンプルを作ってみました。

アニメ途中の諸情報とは何か

それはずばり jQuery.fx コンストラクタの e インスタンスが保持している情報です。

e インスタンスは既に見たように模式図的に示せば以下のような盛りだくさんの情報を内包しています。

■ jQuery.fx コンストラクタの e インスタンス
 e = {
 elem:elem,// 第 1 引数
 prop:propName, // 第 3 引数
 options: {
  queue:false || true, step:function(){・・・}, // これらはユーザーが指定する。
  old,complete,easing,duration,display,overflow,// animateメソッドから引き継がれる。
  curAnim, //animateメソッドの第一引数である prop オブジェクトが格納される。
  orig:{prop},
  show:true || false || undefined, hide:true || false || undefined
 },
 startTime, start, end, unit, now, pos, state,
 // メソッド
 cur(), custuom(), step(), update(), show(), hide()
}

■ jQuery.fx コンストラクタのクラスプロパティ
 jQuery.fx.speeds = {"slow":600, "fast":200, _default:400}
 jQuery.fx.step = {opacity, _default}

▲ToTop

「 アニメ進行過程 」における当該アニメの諸情報は、e.step メソッドがアニメ対象要素( 以下 A )のアニメ対象プロパティ( 以下 props )毎に、ミリ秒単位で静止画を繰り返し描画する過程において、刻々と変化します。

諸情報は e インスタンスオブジェクトのプロパティ【 startTime, start, end, unit, now, pos, state 】に、何百回となく上書きが繰り返されます。

このエントリイではこれらの値を取りだしてみます。

もちろんその全てを取りだしていたら数千,数万のデータとなるため、ここではアニメ継続時間 ( duration )の 15%、30%、50%、75%及び 100%の時間が経過した 5 つの時点に絞って、それぞれの経過時点におけるプロパティ値を取り出すこととします。

なお、折角閲覧して戴くので、閲覧者が操作にある程度関われるよう配慮しました。

第 1 に props(アニメ対象プロパティ)を選択できるように、第 2 に、アニメで使用する easing 関数を多数用意し、これも閲覧者が選択できるようにしました。

アニメ途中経過情報を表示するサンプル

以下がサンプルです。ボックスが 6 つ。左から順にアニメ素材としたボックス、アニメ開始後の時間が、設定済みの継続時間の 15 %進行した段階で止まるボックス、以下 30%、50%、75%でそれぞれ停止するボックス、最後が通常通りアニメーションされるボックスです。なお、アニメ継続時間は、進行過程をゆっくり見せるために一律に 4 秒としました。

4 つの進行時間が異なるアニメの諸情報 ( 各 A の props。具体的には開始時刻, 開始値, 終了値, 経過割合, 値の変化度合, 停止値, 経過時間 ) は下の方に配置した 4 つの表内に、アニメが停止したその瞬間に描画されます。

アニメ
素材
15 %
30 %
50 %
75 %
100%

適用 easing 選択

アニメ継続時間の選択 

アニメ対象プロパティの選択

  
  

▲ToTop

途中停止アニメサンプルの使い方
  1. まず、easing 関数、アニメ継続時間(所要時間と言っても良い)およびアニメーションの対象プロパティを選択します。
  2. 「 Do 隠蔽アニメ 」 ボタンをクリックします。

    これで隠蔽アニメーションが始まり、各々のボックスアニメが指定した途中の段階で停止します。同時にその瞬間に、停止時のアニメに係る諸情報が下の表に出力されます。

    この情報によって jquery.js が処理しているアニメ情報の具体的な内容を知ることが出来ます。

  3. 「 最後まで動かす 」 ボタンをクリックします。これで途中で停止していたアニメが再開されて最後まで進行します。最後まで進行するとアニメ素材以外のボックスは全て隠蔽された状態になります。

    なお、アニメ再開時の諸情報は表には出力されません。

  4. 「 Do 表示アニメ 」 ボタンをクリックします。これによって今度は表示アニメーションが始まります。ここでも途中で停止し、その時の情報が表に上書き出力されます。
  5. 最後に再びアニメを再開させます。「 最後まで動かす 」 ボタンをクリックすれば、表示アニメーションが最後まで進行して、一連の 「 停止─再開 」サンプルアニメーションが終了します。 この段階で初期の 6 つのボックス表示状態に戻ります。

言うまでもなく、easing 関数を変え、継続時間を変え、あるいはアニメ対象プロパティを変えて、何回でも途中停止アニメーションサンプルを起動させることが出来ます。

なお、ボタン、セレクトボックス及びチェックボックスは、アニメーション進行中や途中停止状態の時には使えないように disabled 属性を調整すると共に、カバーボックスをアニメートさせ、使用可能/不能が視覚的にも一目瞭然となるように工夫しました。

所定の継続時間(4000ミリ秒に設定)の 15 % 経過した時点の諸情報

sT:開始時刻, sV:開始値, eV:終了値, s:経過割合,pos:変化度合,
nV:停止値, eT:経過時間, nV = sV + ( eV -sV ) * pos

propertysTsVeV sposnVeT
marginTop
marginRight
marginBottom
marginLeft
borderTopWidth
borderRightWidth
borderBottomWidth
borderLeftWidth
paddingTop
paddingRight
paddingBottom
paddingLeft
width
height
opacity
fontSize
所定の継続時間(4000ミリ秒に設定)の 30 % 経過した時点の諸情報

sT:開始時刻, sV:開始値, eV:終了値, s:経過割合,pos:変化度合,
nV:停止値, eT:経過時間, nV = sV + ( eV -sV ) * pos

propertysTsVeV sposnVeT
marginTop
marginRight
marginBottom
marginLeft
borderTopWidth
borderRightWidth
borderBottomWidth
borderLeftWidth
paddingTop
paddingRight
paddingBottom
paddingLeft
width
height
opacity
fontSize
所定の継続時間(4000ミリ秒に設定)の 50 % 経過した時点の諸情報

sT:開始時刻, sV:開始値, eV:終了値, s:経過割合,pos:変化度合,
nV:停止値, eT:経過時間, nV = sV + ( eV -sV ) * pos

propertysTsVeV sposnVeT
marginTop
marginRight
marginBottom
marginLeft
borderTopWidth
borderRightWidth
borderBottomWidth
borderLeftWidth
paddingTop
paddingRight
paddingBottom
paddingLeft
width
height
opacity
fontSize
所定の継続時間(4000ミリ秒に設定)の 75 % 経過した時点の諸情報

sT:開始時刻, sV:開始値, eV:終了値, s:経過割合,pos:変化度合,
nV:停止値, eT:経過時間, nV = sV + ( eV -sV ) * pos

propertysTsVeV sposnVeT
marginTop
marginRight
marginBottom
marginLeft
borderTopWidth
borderRightWidth
borderBottomWidth
borderLeftWidth
paddingTop
paddingRight
paddingBottom
paddingLeft
width
height
opacity
fontSize

▲ToTop

アニメ途中経過情報を表示するサンプルの要点

アニメ-ション進行過程のあらまし(再掲)

jquery.js の animate メソッドはアニメーション動作開始前に e インスタンスを作成し、そのプロパティ上にアニメーションに係る多数の情報を保持し続け、上書きを繰り返してアニメを実現しています。そのことはアニメーションに関するシリーズエントリイの最初( jquery.js におけるアニメーションコードの解読 ( 1 ) )でも触れました。

その e インスタンスには、数種類のメソッド ( cur, custuom, step, update, show, hide ) も形成され、これらのメソッドが有機的に連携して 「 動き 」 を演出します。animate メソッド内でのこれらのメソッド連携を改めて跡付けてみると次のようになっています。

  1. 最初に animate メソッドの第 1 引数内で指定されたアニメーション終了値を処理します。

    その指定値が show、hide または toggle の時には e.show 又は e.hide メソッドが起動されて、その中で必要な処理を行ってから、e.custum メソッドが呼び出されます。

    指定された終了値がそれらの文字列ではない場合には、その値をanimateメソッド内で評価処理してから( この時に現在値を計算するメソッドが e.cur です )、やはり、e.custum メソッドが呼び出されます。

    こうして animate メソッド内の e インスタンスサブルーチンメソッド連鎖は必ず e.custom メソッドから始まります。

  2. e.custum メソッド内ではアニメ開始時刻を記録し、アニメーションの開始値を取得した後に、e.step メソッドを呼び出します。
  3. e.step は呼び出された時刻を記録し、その時点におけるアニメプロパティの現在値を算出します。この時に現在値を計算するメソッドが e.cur です。
  4. その時点の現在値が決まれば後はブラウザ上に描くだけです。それを e.update メソッドが行います。
  5. この過程が何百回、何千回となく繰り返されて 「 動き 」 が描かれます。
アニメ途中で何かを実行させるメソッド: e.options.step

さて、アニメーション実行中にユーザーが出来ることは、ブラウザを停止する、パソコンの電源を切る等の外的作用の他には、(1) アニメが終わるのを待つか、(2) e.options.step メソッドをあらかじめ指定しておき、アニメーション進行途中でそれを実行させるか、この 2 択 しかありません。アニメを途中で停止させる指示を与える場合も、e.options.step メソッド内に $().stop メソッドをあらかじめ仕込んでおくことになります。

ここでは (2) の途中で何かをさせるメソッド e.options.step の指定方法について言及します。

e.options.step の指定について(jquery.js #4018 参照)

このメソッドは e.update メソッドの冒頭で起動されます。アニメ対象プロパティ毎に、数ミリ秒毎に繰り返し実行される e インスタンスメソッドチェーン内において、何百回、何千回と描画が繰り返されますが、その描画直前毎に、e.options.step メソッドが実行されます。

この e.options.step の引数は、アニメプロパティ現在値と e インスタンスの 2 つで、起動元の this.elem と併せて jquery.js #4018 で無条件に指定されます。起動元はアニメーション対象要素タグに指定されますから、e.options.step メソッド内において this キーワードを使った場合、その参照先は対象要素タグとなります。

また、 引数に e インスタンスがあると言うことはアニメに係る全ての情報に、 e.options.step メソッドからアクセスできることを意味しています。この点は極めて重要なポイントです。

以上の実行タイミングと引数を踏まえた約 200 行のコードによって、このエントリイのサンプルを実現しました。具体的な方法は、後に掲載したコードの通りですが、要点を記しておきます。

サンプルコードの要点

ユーザーフレンドリイに!

これまでは簡単に言えば「 面倒なので 」(苦笑)、form 要素の使用を出来るだけ避けてきました。せいぜいボタンを単独で多用してきた程度で、リストボックス、コンボボックス、チェックボックスなどのブラウザ閲覧者が操作し、その結果を反映させられるツールは殆ど使ってきませんでした。

その意味では今回のチャレンジは、step メソッドの使い勝手を試してみる以外に、form 要素を駆使するコーディングに真正面から取り組んだ点が大きな特徴であり、またそれがハードルともなりました。

ここで配置した form 要素の使いやすさについては閲覧者の批判を甘受するしかありませんが、アニメ中に form 要素を変更されなくするための、思いつきで作った二重の処理( disabled 属性の処理と隠蔽カバーをアニメで被せたこと)には、それなりに満足しています。

なお、このカバーアニメーションにおいてもユーザー指定の easing 関数を使うように設計してあります。

途中停止のための動作ステータスキャッチ

options.step メソッドにおけるアニメの途中停止は、動作ステータス ( e.state ) の操作により行いました。

プロパティ値の変動割合で行うことも可能ですが、この値は easing 関数によって増減する動的なものなので、時間経過によって途中の段階を捉える方が確実だと考えました。

e.state は 或る e.step メソッドの 「 呼び出し時迄の経過時間÷アニメ継続時間 」で定義されている、「 経過時間割合 」とでもよぶべきものですが、この値は決して数直線のような連続的な数値にはなりません。

当該要素の当該プロパティ毎に、繰り返し呼び出される e.step の呼び出し時迄の経過時間は必ず一定間隔が空くからです。ちょうど 15 %過ぎた、そのタイミングの時に、偶々 e.step が呼び出されているという保証は全くありません。

このため 15 %前後に一定の幅を持たせた 「 許容域 」を設定しないと、アニメ停止のタイミングが捉えられません。このため、if ( e.state > (( stopPoint - permitter )/100 ) && e.state < (( stopPoint + permitter )/100) ) と言う式によって、一定の許容域に収まるタイミングを捉えて、その瞬間にアニメを停止させるようにしました。stopPoint は 15 %等の値、permitter は許容幅です。

諸情報の表における s 列がその許容域内で補足されたことを示す経過時間割合です。

しかも、許容幅はアニメ継続時間との関係で変動させる必要がありました。余り短時間の継続時間だと e.step の起動回数が少なくなるので、許容幅を広げないと「 その瞬間 」を捉えられないのです。こうして継続時間が長ければ許容幅は小さく、継続時間が短い場合には長くなるよう、動的に変動するようにしました。

4 つの機能を持たせたアニメ起動ボタン

出来るだけ簡単な操作になるよう、隠蔽アニメ起動、隠蔽アニメ停止後の再開、表示アニメ起動、表示アニメ停止後の再開───という一連の操作を全て 1 つのボタンだけで行うようにしました。

その結果コードは複雑にならざるを得ませんでした。4 回のそれぞれの操作の意味が全て異なるため、それぞれ毎に異なるコーディング他必要となるためです。これを click イベントの Toggle メソッド利用と、その中の 2 つの関数の起動回数(奇数回か偶数回か)によって 4 つに分けるようにしました。

IE 対策

今回もまた IE 対策を施さねばなりませんでした。W 3 C 準拠になったはずの IE 8 においてもまだ、準拠が不十分な点が残っているのでしょう。

具体的にはアニメーション対象プロパティとしてフォントサイズを指定した場合の対応です。Firefox の場合には全く問題がなかったのですが、フォントサイズをアニメ対象とした場合において、IE においては初期値のフォントサイズを指定しないと、アニメ前のフォントサイズを勝手に最大サイズ( おそらく96 pt )と解釈してしまうのです。

仕方なく、アニメ領域だけフォントサイズを指定せざるを得ませんでした。

Web サイト作成上、フォントサイズを固定的に指定することは推奨されていません。ユーザーがブラウザの拡大/縮小機能を使った場合に、フォントサイズが固定されているとフォントだけ拡大/縮小されないからです。ですから私の場合フォントサイズ指定は全く行ってこなかったのですが、今回の場合やむを得ず指定せざるを得ませんでした。

アニメ停止後再開時点における、開始値と終了値の操作に最も苦労しました

隠蔽アニメーションは開始時点のプロパティ値( 開始値 )を e.hide メソッド内で取得し、表示アニメは終了時点のプロパティ値( 終了値 )を e.show メソッド内で取得します。そして e.hide メソッドはアニメ対象要素の属性値や style 値を一切変化させません。だからこそ、e.show メソッドの終了値は、その直前に行われた e.hide メソッドの開始値に等しくなります。

ここに、開始値も終了値も共に、e.cur メソッドによって、アニメ対象タグの (1) 属性値や (2) style オブジェクト値、あるいは (3) 算出スタイル値から算出されます。

さて、停止後に再開されるアニメの初期値は、停止時点のプロパティ値となります。そして、停止時のプロパティ値は、既に動いた後なのですから、最初にアニメを起動した時のプロパティ値とは決して同一ではありません。

従って、アニメ対象プロパティを何も変更せず、単純に停止していたアニメを再開させると、再開後の最終値は当初の設定値と異なってしまいます。

例えばここのサンプルで言えば、15%で停止させたアニメを再開して隠蔽させ、その後最後まで表示させると、その表示サイズは 15% で停止した時のサイズであって、決して当初の大きさまでは戻りません。

この問題をどうやって解決するか───この点こそ、今回のコーディングで最も頭を悩ませた箇所でした。

一旦 animate メソッドが起動してしまうと、その途中でアニメ対象プロパティをユーザーが直接変更することは、options.step メソッドを使ってアニメを進行途中で止めたとしても困難です。

何故ならば、アニメ対象プロパティは、既に animate メソッドによって作成済みの e インスタンスオブジェクト内の各プロパティに値が格納されてしまっていて、コードによって e インスタンスを操作することは出来ないからです。

出来ることは、e インスタンスのメソッドが作動する際に、style オブジェクト、要素属性又は算出スタイルから、各プロパティ値を算出することに着目して、要素の style オブジェクトの各プロパティ値や、定義できる属性であれば要素属性値を、次の animate メソッドが始まる前に、強制的に書き換えてしまい、それによって次の animate メソッドの開始値や最終値を前もって変更してしまうことだけです。

このことに気がついた結果、途中停止後に再開したアニメーションも、最初のアニメーションの開始値と終了値を参照するように強制的なコーディングを施して、やっとこのサンプルが完成したのでした。

▲ToTop

 #animArea_725 {
  position: relative; width:620px; margin:1em auto; height:140px;
  background-color:lavender;border:0;font-size:16px;
 }
 #box0_725, #box15_725, #box30_725, #box50_725, #box75_725, #box100_725 {
  position:absolute; top:10px; width:64px; height:84px; padding:3px;margin:5px;
  background-color:darkgreen; border:8px ridge bisque;
 }
 #box0_725 {
  left:10px;
 }
 #box15_725 {
  left:110px;
 }
 #box30_725 {
  left:210px;
 }
 #box50_725 {
  left:310px;
 }
 #box75_725 {
  left:410px;
 }
 #box100_725 {
  left:510px;
 }
 #formBlock {
  position:relative; width:600px;height:220px;margin:1em auto; border:1px dotted aquamarine;
  line-height:1.1em; clear:both;
 }
 #fm_725 {
  position:absolute; z-index:2; width:170px; margin:1em;
 }
 #fm_725 p{
  color:black;background:ivory;text-indent:-0.5em;text-align:center;line-height:1.2em;
  width:170px;
 }
 #fm2_725 {
  position:absolute;left:190px;margin:1em;
  width:345px; padding:10px;color:black;background:ivory;
  height:175px;
 }
 #submit_725 {margin:0.5em 1em 1em; padding:5px;border:2px solid royalblue;text-align:center;}
 #fm1shutter_725 {
  position:relative; z-index:2; top:-176px;
  width:170px; height:178px; background:teal; display:none;
 }
 #fm2shutter1_725 {
  position:absolute; z-index:2; left:6px;top:8px;
  width:350px; height:130px; background:teal; display:none;
 }
 #fm2shutter2_725 {
  position:absolute; z-index:2; left:6px;top:142px;
  width:350px; height:50px; background:teal; display:none;
 }
 input,button {overflow:visible; padding:2px;}
■ Javascript codes
(function(){
 var $fm2 = $("#fm2_725"), $chk = $(".chkbox"), $done = $("#go"),$initAnim = $("#initAnim_725"),
  $box0=$("#box0_725"), $box1=$("#box15_725"), $box2=$("#box30_725"),
  $box3=$("#box50_725"), $box4=$("#box75_725"), $box5=$("#box100_725"),
  $t1=$("#table1_725 tr"), $t2=$("#table2_725 tr"),
  $t3=$("#table3_725 tr"), $t4=$("#table4_725 tr"),
  $shutter1=$("#fm1shutter_725"), $shutter2=$("#fm2shutter1_725"), $shutter3=$("#fm2shutter2_725"),
  selEasing="swing", // 選択された easing 関数を入れる変数。既定値はswingとしておく
  chkList=[], // 選択されたチェックボックスリスト
  animProp=[], animaCSS, boxCSS={},initCSS=[], dur=4000, //既定値4秒;
  permitter=0.9, // パーセント許容幅(既定値0.9)
  odd = [false,false] , // 第 1 関数及び第 2 関数の起動回数が奇数かどうかを odd 配列に記録
  ival, obj={},
  fxAttrs = [
   ["marginTop","marginRight","marginBottom","marginLeft"],
   ["borderTopWidth","borderRightWidth","borderBottomWidth","borderLeftWidth"],["paddingTop","paddingRight","paddingBottom","paddingLeft"]
  ], 
  attrs=[];

 // 全プロパティ名の単純なリスト配列作成
 for (var i=0; i<fxAttrs.length; i++)
  for (var j=0; j<fxAttrs[i].length; j++)
   attrs.push(fxAttrs[i][j]);
 attrs = attrs.concat("width","height","opacity","fontSize");

 // easing リストから 選択された easing 関数を知る
 $("#sel_725").change(function(){
  selEasing = $(this).val();
 });

 // アニメ継続時間コンボボックスから 選択された duration 値を dur 変数に代入する
 $("#fm2_725 select").change(function(){
  dur = +$(this).val();
  permitter = (dur >2000) ? 0.9 : (dur > 800) ? 3 : 4;// 許容値を継続時間毎に変化させる
 });

 // チェックボックスの既定値選択設定
 $("#defaultSelect_725").click(function(){
  $("#fm2_725 input:checkbox").val(
   ["margin","padding","width","height","opacity"]
  );
 });
 // チェックボックス全選択設定
 $("#allSelect_725").click(function(){
  $("#fm2_725 input:checkbox").val(
   ["margin","border","padding","width","height","opacity","fontSize"]
  );
 });

 // アニメ開始前の全てのCSSプロパティ値を initCSS 配列に記録する
 $.each([$box1,$box2,$box3,$box4],function(){
  for (var j=0; j<attrs.length; j++)
   boxCSS[ attrs[j] ] = this.css( attrs[j] );
  initCSS.push(boxCSS);
 });

 // アニメ用のCSSオブジェクト( margin、borderWidth、padding )を作成するmeta関数
 var makeCSS =function(i,type){
  $.each( fxAttrs[i],function(){obj[this]=type;} ); return obj;
 }

 // チェックボックス情報を踏まえたアニメ用の CSS オブジェクト作成
 var make_animObj = function(type){
  var retObj={}, mStr = chkList.join();
  if (mStr.match("margin")) retObj = $.extend(retObj,makeCSS(0,type));
  if (mStr.match("border")) retObj = $.extend(retObj,makeCSS(1,type));
  if (mStr.match("paddin")) retObj = $.extend(retObj,makeCSS(2,type));
  if (mStr.match("width")) retObj = $.extend(retObj,{width:type});
  if (mStr.match("height")) retObj = $.extend(retObj,{height:type});
  if (mStr.match("opacity")) retObj = $.extend(retObj,{opacity:type});
  if (mStr.match("fontSize")) retObj = $.extend(retObj,{fontSize:type});
  for (var p in retObj) animProp.push(p); // アニメ対象プロパティリストを animProp 配列に格納
  return retObj;
 }

 // 一時停止時の情報を table に書き込む
 function printOut(o,box){
  var startTime =o.startTime.toString();
  var output=[
   startTime.substring(startTime.length-5,startTime.length), o.start, 
   Math.round(o.end*100)/100,
   Math.round(o.state*10000)/100+"%", Math.round(o.pos*10000)/100+"%",
   Math.round(o.now*10000)/10000, parseInt(dur * o.state)
  ];
  for (var i=0;i<16;i++){
   if (attrs[i]==o.prop ){
    for (var j=0; j<7; j++)
     box===$box1 ? $t1.eq(i+1).children().eq(j+1).text(output[j]) :
     box===$box2 ? $t2.eq(i+1).children().eq(j+1).text(output[j]) :
     box===$box3 ? $t3.eq(i+1).children().eq(j+1).text(output[j]) :
     box===$box4 ? $t4.eq(i+1).children().eq(j+1).text(output[j]) : null;
   }
  }
 }
 // goAnim メソッドから呼び出されて一時停止時の情報を取得する
 function doStep(box,obj,stopPoint){
  box.animate(obj,{
   step:function(){
    if (stopPoint==100) return;
    var e = arguments[1];
    if (e.state > ((stopPoint-permitter)/100) && e.state < ((stopPoint+permitter)/100)){
     var o = $.extend({},e);
     if (o.prop === animProp[animProp.length-1]) {
      box.stop();
     }
     printOut.call(null,o,box);
    }
   },
   duration:dur, easing:selEasing
  });
 }
 // type="toggle" で show/hide を循環させる
 function goAnima(type){
  var stopTime =[];
  stopTime =[15,30,50,75,100];
  animaCSS = make_animObj(type);
  $.each([$box1,$box2,$box3,$box4,$box5],function(i){
   doStep(this,animaCSS,stopTime[i]);
  });
 }

 // アニメと対象画像の初期化
 $initAnim.click(function(){
  $.each([$box1,$box2,$box3,$box4],function(i){
   this.stop(true,true);
   for (var j=0; j<attrs.length; j++){
    $.attr(this.get(0).style, attrs[j] ,initCSS[i][attrs[j]]);
   }
  });
  $(this).blur();
 });

  // select オプション・チェックボックス・コンボボックスの使用不能/可能を切り替える
 function elementsDisabled(type){
  $("#fm_725 option").attr("disabled",type).get(0).disabled="disabled"; // easingリストボックス
  $("#fm2_725 option").attr("disabled",type); // duration コンボボックス
  $("#fm2_725 input:checkbox").attr("disabled",type); // プロプ選択ボックス
  $("#defaultSelect_725").attr("disabled",type); // 既定選択ボタン
  $("#allSelect_725").attr("disabled",type); // 全選択ボタン
 }

  // チェックボックスのオンオフ状態をチェックし、
  // チェックボックス・コンボボックス・セレクトリストを使用不能とする
  function checkChkbox(){
  chkList=[]; // 必ず初期化する必要がある
  $("#fm2_725 input:checkbox:checked").map(function(){
   chkList.push( this.value );
  }); // オンされたチェックボックスリストを配列 chkList に格納
  if (!chkList.length) { // No check 時には警告を発し、アニメを起動させない。
   alert("アニメ対象プロパティを何か 1 つ以上指定してください");
   return;
  }
  elementsDisabled("disabled"); //全て使用不可とする
 }

 // 囲み内ボタンの使用可確認とフォーム要素のカバー不透明度設定
 $done.attr("disabled","");
 $initAnim.attr("disabled","");
 elementsDisabled(""); // 起動時には form elements を全て使用可能とする
 $.each([$shutter1,$shutter2,$shutter3],function(){
  if (this.css("opacity")==1) this.css("opacity","0.5");
 });
 // 2 つの form 内の、2 つのボタンの使用可/不可状態の設定とシャッターアニメの起動設定
 function setForm(type,duration,easing){
  $done.attr("disabled",type);
  $initAnim.attr("disabled",type);
  var disp = (type==="disabled") ? "show" : "hide";
  if ( disp=="show" && odd[0] !== odd[1] || disp=="hide" && !odd[0] === !odd[1] ){
    $shutter1.animate({width:disp,height:disp},duration,easing);
    $shutter2.animate({width:disp,height:disp},duration,easing);
  }
  $shutter3.animate({width:disp,height:disp},duration,easing);
 }

 // アニメが動作中か否かを確認し、状態に応じて form 要素の使用不可/可を切り替える
 function isbusy(){
  ival =setInterval(function(){
   if ( $box1.is(":animated") || $box2.is(":animated") || $box3.is(":animated") || $box4.is(":animated") ) {
    setForm("disabled","slow",selEasing);
   } else {
    setForm("","slow",selEasing);
    clearInterval(ival);
    ival=undefined;
   }
  },20);
 }

 $done.toggle(function(){ // click toggle
  odd[0]=!odd[0]; // 奇数か偶数かを反転させる
  isbusy();
  checkChkbox();
  goAnima("toggle");
  $done.attr("value","最後まで動かす");
  $(this).blur();
 },function(){ // 奇数回目はe.hide 、偶数回目はe.showメソッドとなる
  if (isbusy()) return;
  odd[1] = !odd[1]; // 奇数か偶数かを反転させる
  for (var p in animaCSS) animaCSS[p] = odd[1] ? "hide" : null;
  $.each([$box1,$box2,$box3,$box4],function(i,n){
   if (!odd[1]) { // 偶数回目つまり e.show メソッドの2度目の起動前に
    for (var p in animaCSS) // 終了値を強制的にアニメ開始値とする
     animaCSS[p] = initCSS[i][p];
   }
   this.animate(animaCSS,dur,selEasing,function(){ // complete funcを指定
    for (var j=0; j<attrs.length; j++) // CSS style オブジェクトの各値を初期値に戻す。
     $.attr(n.get(0).style, attrs[j] ,initCSS[i][attrs[j]]);
    if (i==3) { // 75%ボックスが最後まで表示されたら
     $done.attr("value",odd[1] ? "Do 表示アニメ" : "Do 隠蔽アニメ");
     elementsDisabled(""); //全て使用可とする
    }
   });
  });
  $(this).blur();
 });
})();

jquery.js におけるアニメーションコードの解読 ( 7 )

ボックスアニメーションのサンプル

このエントリイでは jquery.js におけるアニメーションコードのこれまでの解読を踏まえて、一寸一息いれることとします。アニメサンプルを多数用意して、その解説を試みます。

ここでいう、ボックスアニメーションとは私が勝手に名付けた名称ですが、配置指定された div 要素のアニメーションを意味しています。

個々のボックスのアニメーションを 12 種類、その他これらのボックスを利用したアニメーションを 6 種類、計 18 種類のアニメを作成し、その挙動に関する解説を行い、末尾には作成したスクリプトを付けました。

18 種類のアニメーションサンプル

下のボタンをクリックすると隠蔽/表示アニメーションが起動します。 各々のボタンには、隠蔽と表示の 2 機能を持たせました。

ぼっくす 1
toggle()
ぼっくす 2
toggle(2000)
ぼっくす 3
起終点:中心
ぼっくす 4
起終点:右上
ぼっくす 5
起終点:右下
ぼっくす 6
起終点:左下
ぼっくす 7
起終線:上
(文字も変化)
ぼっくす 8
起終線:左
(文字無変化)
ぼっくす 9
起終点:左上(文字も変化)
ぼっくす 10 width だけ toggle
ぼっくす 11 borderWidth だけ toggle
ぼっくす 12 borderWidthをAnim 

▲ToTop

18 のサンプルアニメーションの解説

1 : $().toggle() の挙動

jQuery インスタンスの toggle メソッドの例です。引数なしでこのメソッドを起動すると、$(htis).is(":hidden") メソッドによって当該要素の隠蔽/表示状態をチェックします。

その結果に応じて、もし true ならば $(this).hide() が、あるいは false ならば $(this).show() がそれぞれ起動され、それぞれのメソッドによって、this.style.display.none と this.style.display.block の切り替えが行われ、隠蔽と表示がアニメートされます。「時間」に係るコードは全く含まれないので、瞬時に行われる単純なアニメとなります。

2 : $().toggle(2000) の挙動

1 と同じ jQuery インスタンスの toggle メソッドの例ですが、動作時間を指定した引数がある点が異なります。

この引数があると toggle メソッドの定義から animate(genFx("toggle",3),"slow") メソッドが起動され、この genFx("toggle",3) 関数実行によって margin、padding 及び content サイズ( height と width )並びに opacity の 4 つの( 正確には margin と padding にはそれぞれ左右上下があるので、2×4+3 で 11種類の )プロパティがアニメーション対象となります。

ここで興味深いことは border がアニメ対象になっていないということです。当然のことですが、color プロパティと違って、border は jquery.js のアニメに馴染まない訳ではありません。実際 borderWidth を問題なくアニメ対象プロパティとして扱えることは、上のサンプル 11&12 で示した通りです。

※ 実は color をアニメ対象とする jquery 拡張版 ui があります。effects.core.js の中に含まれています。

そもそも、border には幅の他に、値が数値ではない色とスタイルの属性があります。そのため border そのものはアニメ対象プロパティから除外したのかもしれません。borderWidth だけでもアニメ対象プロパティにしてもおかしくないはずなのですが、fxAttrs の対象にしなかったのはどうしてでしょうか。理由は判然としません。

さて、上の 11 のアニメ対象プロパティを対象として、animate メソッドの沢山のコードが進行します。animate メソッドの第 1 引数に genFx("toggle",3) が指定されたことにより、jQuery.fx のインスタンス e から、e.hide() や e.show() メソッドが起動され、そこから e.step() メソッドが起動されます。

こうして、11 のプロパティの値が、当初の表示状態値と 1 ( e.show() メソッドにおいて width と height プロパティを扱う場合 ) 又は 0 ( その他のプロパティの場合 )の間を漸減/漸増させられて、アニメーションが引き起こされます。

最後に、$().toggle(2000) メソッドが、アニメ対象要素の左上に収斂し、その左上から展開されることに留意する必要があります。何故ならば、e.hide() や e.show() メソッドは、margin、padding、width、height を 0 に( e.show() メソッドの場合 width と height は 1 に )遷移させるのですから、左上に収斂しそこから展開することになるのです。

更に、これらのメソッドは不透明度も 0 と 1 の間を漸減/漸増させるため、不透明と透明の間を行き来することになります。

ここで触れたアニメの終点/起点の位置指定こそ、animate メソッドを扱う上での最も重要なポイントです。

▲ToTop

3 : ボックス中心を起終点とするアニメの挙動

ボックス 2 では左上がアニメの起終点でした。当然その起終点位置を変えてみたくなります。

「 $().toggle メソッドを使う前提で、どうすれば起終点位置を変えられるか? 」───かなり悩みました。toggle メソッドは e.show() や e.hide() メソッドを呼び出しますが、その中でプロパティ値は 0 又は 1 に変更されます。つまり起終点は左上です。そして toggle メソッドの定義から、何らかの引数により位置指定を変えることは不可能です。結局、位置指定は toggle メソッドでは行えません。このメソッドでは必ず左上に収斂し、そこから展開するアニメしか演出できません。

そしてさんざん考え抜いた結果ついに...。
「 位置指定を別のメソッドで行い、かつそれを toggle メソッドと並行起動すればよい」───このことに気がつきました。

起終点位置だけを指定するアニメーションメソッドは、基本的に top、left の初期値と終了値を適切に設定することになります。(アニメ対象要素は一般に position 指定を「絶対」か「相対」に指定しますので top 及び left が指定できます。)

具体的な対応方法はこのエントリイ最下部に掲載したコードを見て戴くとして、苦労した点を記録しておきます。

ボタンクリックでアニメを励起させていますが、そのボタンはボックスの表示アニメにも、あるいは隠蔽アニメにも、両刀使いするように設計してあります。

そして、初期状態がボックスの表示となっているのですから、toggle メソッドの別の使い方( click の度に 2 つの関数を交互に起動する toggle メソッド )を利用する方法もあります。しかしここでは、もっと一般的な方法を探りました。

ここで提示したアニメサンプルの場合、収斂/展開するアニメーションの起終点(線)を同一にしてあります。それが自然だからですが、同一の点や線から始まり/終わるアニメの場合、その初期値と終了値は正負の符号が異なる同一の絶対値となります。そこで、ボタンのクリック回数が奇数回か偶数回かによって、外サイズ取得値の正負を反転させて、起終点( あるいは起終線 )位置とするようコーディングしました。

▲ToTop

4 : ボックス右上を起終点とするアニメの挙動

今度はボックスの右上を起終点とするアニメです。style.top ±= 0 及び style.left ±= box 外幅から、起終点値を出しました。

隠蔽/表示のトグル起動は 3 と同様の奇数回/偶数回による方法で対処しました。以下 5、6 共同様。

5 : ボックス右下を起終点とするアニメの挙動

ボックスの右下を起終点とするアニメです。style.top ±= box 外高さ及び style.left ±= box 外幅から、起終点値を出しました。

6 : ボックス左下を起終点とするアニメの挙動

ボックスの左下を起終点とするアニメです。style.top ±= ボックス外高さ 及び style.left ±= 0 により起終点値を出しました。

7 : ボックス上辺を起終線とするアニメの挙動

ここからアニメの趣が変わります。これまでは全て $().toggle メソッドを利用してきました。起終点位置を ( 0, 0 ) 以外とするために animate メソッドを併行起動させた場合も、全て toggle メソッドとの併行起動でした。

ところが 7、8、10 ~ 12 番目のサンプルは toggle メソッドを使っていません。但し、12 番目のサンプル以外はアニメ用 CSS オブジェクトのプロパティ値を "toggle" としていますので、プロパティ値を "toggle" とした場合の animate メソッドのコーディングによって、不透明度はアニメ対象になりませんが、縮小するアニメの場合には必ず最後に消滅し、展開する場合には最初に表示されます。

さて、7 番目のサンプルでは、縦方向のサイズに係るプロパティだけ( height、marginTop、marginBottom、paddingTop 及び paddingBottom )をアニメ対象とした上で、更に文字サイズもアニメ対象に追加してみました。

起こされるアニメは slideUp メソッドに酷似していますが、slideUp メソッドは使用せず、fxAttrs 配列を拡張した exAttrs と、genFx と全く同一な Fx メソッドを使用してアニメートさせました。
( ここに独自に定義した配列とメソッドは、共に無名関数内のローカル変数としました。)

▲ToTop

8 : ボックス左辺を起終線とするアニメの挙動

7 の変形でボックス左辺に畳み込み、そこから拡幅するメソッドです。文字サイズはアニメ対象としていません。アニメ用プロパティ値に "toggle" を指定しているので、縮小後に隠蔽され( display.none )、展開直前に表示が再開( display.block )されます。

9 : ボックス左上を起終点とし文字サイズも変えるアニメの挙動

このアニメも exAttrs と Fn を活用して極めて短いコーディングで作りました。ボックス 2 の挙動に文字の変化を加えたものです。

10 : width だけをアニメ対象プロパティとした挙動

アニメプロパティとして width だけを対象と、animate メソッドの第 1 引数であるアニメ用 CSS プロパティ値を "toggle" としました。従って、不透明度はアニメ対象とはなりませんが、縮小後に隠蔽され、展開直前に表示が再開されます。

11 : borderWidth をアニメ対象とした挙動

jquery.js では borderWidth を標準的なアニメ操作対象としていないので、敢えてそれを対象としたアニメを作ってみました。

ここでも、animate メソッドの第 1 引数であるアニメ用 CSS プロパティ値を "toggle" としたので、不透明度はアニメ対象とはなりませんが、外枠線が細くなっていって最終的にボックスは hide され、他方、枠線が太くなる前にボックスが show されます。

12 : borderWidth だけを animate させ、かつ step メソッドを利用した挙動

最後のサンプルはボーダーサイズだけを animete メソッドの対象とし、かつ、toggle 値を使っていないので、border 幅がゼロに縮小されても決してボックスは隠蔽されません。

また、animate メソッドの第 2 引数 speed をオブジェクトとして指定し、このサンプルだけ唯一 e.options.step メソッドを利用して、アニメーションの起動回数をカウントさせました。こうして 12 のサンプルの中で最もカスタマイズ度の高いものになりました。

アニメーションを起動すると、起動回数がボックス内文字列の最後に追加され、ストップウォッチのようにカウンタ数が変化します。

この animate メソッドではアニメ対象プロパティを 4 つ指定していますので、各プロパティ毎の起動回数は、カウンタ値を 4 で割れば算出できます。

なお、アニメーションの起動回数は、duration 指定時間、CPU の性能、メモリの空き具合、ブラウザの Javascript 解析/実行性能等に左右されますから、同じ duration 指定であってもブラウザとパソコンの性能毎に異なることになります。

▲ToTop

13 : 全てのアニメを一斉に起動する挙動

12 番目までのサンプルを一斉にアニメーションさせたいと思い、それを実行しました。

スクリプトは、ボタン click メソッドの引数において、各アニメ起動関数を対象として each メソッドで巡回処理しているだけです。

14 : 全てのアニメを順次起動する挙動

12 番目までのサンプルを一斉にアニメーションさせた次には、一斉ではなく順次起動させることもおもしろい、と思い立ちそれを実行しました。

スクリプトでは、ボタン click メソッドの引数における 12 番目までのアニメ起動関数を、setTimeout 関数を使って一定間隔で遅延させながら巡回処理しました。

15: ランダムに 2 つのボックスを選択させて入れ替える挙動

一斉及び順次起動するアニメーションの次には、乱数関数を使って任意に 2 つのボックスを選択させ、それらの位置を互いに入れ替えるアニメーションを考えました。2 つのボックスの top 値と left 値を相互に入れ替えるだけなので決して複雑なコーディングは要りません。

なお、ここでは 弾性的動きを引き起こす easing 関数を採用してみました。この結果「イヤイヤしながら仕方なく移動する」ような動きとなりました(爆爆WWW)

16: 6 つのボックスを同時に入れ替える挙動

2 つのボックス入れ替えをやってみたら、もっと多くのボックスを同時に入れ替えてみたいと思い、それを実現しました。

注意した点は、必ず 異なる 6 つのボックスを選択させなければならない、ということです。1 ~ 12 迄の乱数を乱数関数で発生させ、そのうちの 6 個を使うわけですから、関数からの返値が同じ値になる可能性は非常に高いわけです。

大変冗長なコードになりましたが、返値について 1 つずつ同じかどうかチェックし、同じ場合には乱数発生をやり直して、必ず 6 つの値が異なるようにしました。

なお、折角の機会なので easing 関数は全て異なるものを使ってみました。

17: ボックス位置交換をタイムラグを持たせて行う挙動

更に、ボックス入れ替えを同時にではなく、タイムラグを持たせて実行してみたい、と思いそれを実現しました。

タイムラグは setTimeout 関数で animate メソッドの起動を遅延させて実現しました。

18: ボックスの並び順を整列する挙動

12 個全てのボックスを瞬時に番号順に整列します。ボックスの番号を頼りに top 値と left 値を算出する代数式を考え、コーディングして全ボックスを瞬時に整列するようにしました。入れ替わった箇所だけを元に戻すのは大変なので、簡便で安易な方法を採用しました(^^;。

上の 18 アニメサンプルに関するスタイルシートとスクリプト

■ スタイル設定
 #animArea {
  width:485px; height:440px;margin-top:1em;
  position:relative; background:lightcyan;
  padding:10px;
  text-align:center;
  line-height:1.2em;
 }
 button.btn {margin:5px; overflow:visible; padding:2px;}
 #box1_724, #box2_724, #box3_724, #box4_724, #box5_724, #box6_724,
 #box7_724, #box8_724, #box9_724, #box10_724, #box11_724, #box12_724 {
  position:absolute;left:10px;
  margin:10px 10px 10px 10px;
  border:palegoldenrod 10px ridge;
  padding:5px;
  width:110px; height:60px;
  background:royalblue;
 }
 #box4_724, #box5_724, #box6_724 {
  top:120px;
 }
 #box7_724, #box8_724, #box9_724 {
  top:230px;
 }
 #box10_724, #box11_724, #box12_724 {
  top:340px;
 }
 #box2_724, #box5_724, #box8_724, #box11_724 {
  left:170px;
  border:lightblue 10px outset;
 }
 #box3_724, #box6_724, #box9_724, #box12_724 {
  left:330px;
  border:plum 10px inset;
 }
■Javascript
(function(){
 var exAttrs = [
  // height animations
  [ "height", "marginTop", "marginBottom", "paddingTop", 
   "paddingBottom", "borderTopWidth","borderBottomWidth" ],
  // width animations
  [ "width", "marginLeft", "marginRight", "paddingLeft",
   "paddingRight", "borderLeftWidth","borderRightWidth" ],
  // opacity animations
  [ "opacity" ],
  // fontsize animations
  [ "fontSize" ]
 ];
 function Fn( type, num ){
  var obj = {};
  jQuery.each( exAttrs.concat.apply([], exAttrs.slice(0,num)), function(){
   obj[ this ] = type;
  });
  return obj;
 }
 var i=1,j=13,$btn=[],$box=[],hidden=[],even=[],w=[],h=[],flag=[],duration=2000;
 for (; i<j; i++) {
  $btn[i] = $("#btn" + i +"_724");
  $box[i] = $("#box" + i + "_724");
  hidden[i] = $box[i].is(":hidden");
  w[i] = !hidden[i] && $box[i].outerWidth() || 0;
  h[i] = !hidden[i] && $box[i].outerHeight() || 0;
  flag[i]=0;
 }
 for (var i=13; i<19; i++) {
  $btn[i] = $("#btn" + i +"_724");
 }
 $("button.btn").hover(
 	function(){$(this).css("background-color","paleturquoise")},
 	function(){$(this).css("background-color","")}
 ).toggle(
  function(){$(this).css("background-color","thistle")},
  function(){$(this).css("background-color","palegreen")}
 );

 if(!$box[12].is(":hidden")) {
  var bordLeft =  $box[12].css("border-left-width"),
  bordRight = $box[12].css("border-right-width"),
  bordTop = $box[12].css("border-top-width"),
  bordBottom = $box[12].css("border-bottom-width");
 }
 $box[12].append("<span/>");

 function anim1(){$box[1].toggle();}
 function anim2(){$box[2].toggle(duration);}
 function anim3(){
  var even = ++flag[2] % 2 === 0,
   leftAnim=( ( hidden[3] + even ) ? "-=": "+=" ) + w[3]/2 +"px",
   topAnim=( ( hidden[3] + even ) ? "-=" : "+=" ) + h[3]/2 +"px";
  $box[3].toggle({queue:false,duration:duration})
   .animate( {left:leftAnim, top:topAnim},duration );
 }
 function anim4(){
  var even = ++flag[3] % 2 === 0,
   leftAnim=( ( hidden[4] + even ) ? "-=": "+=" ) + w[4] +"px";
  $box[4]
   .animate( {left:leftAnim},{queue:false,duration:duration} )
   .toggle(duration);
 }
 function anim5(){
  var even = ++flag[5] % 2 === 0,
   leftAnim=( ( hidden[5] + even ) ? "-=": "+=" ) + w[5] +"px",
   topAnim=( ( hidden[5] + even ) ? "-=" : "+=" ) + h[5] +"px";
  $box[5]
   .animate( {left:leftAnim, top:topAnim},{queue:false,duration:duration} )
   .toggle(duration);
 }
 function anim6(){
  var even = ++flag[6] % 2 === 0,
   topAnim=( ( hidden[6] + even ) ? "-=" : "+=" ) + h[6] +"px";
  $box[6]
   .animate( {top:topAnim},{queue:false,duration:duration} )
   .toggle(duration);
 }
 function anim7(){$box[7].animate(
  $.extend(Fn("toggle",1),{fontSize:"toggle"}),duration
 )}
 function anim8(){$box[8].animate({
  width:"toggle",marginLeft:"toggle",marginRight:"toggle",
  paddigLeft:"toggle",paddingRight:"toggle",borderLeftWidth:"toggle",
  borderRightWidth:"toggle"},duration
 )}
 function anim9(){$box[9].animate(Fn("toggle",4),duration);}
 function anim10(){$box[10].animate({width:"toggle"},duration)}
 function anim11(){$box[11].animate({
  borderLeftWidth:"toggle", borderRightWidth:"toggle",
  borderTopWidth:"toggle", borderBottomWidth:"toggle"
 },duration)}
 function anim12(){
  var k=0, even = ++flag[12] % 2 === 0,
  leftAnim = (( hidden[12] + even ) ? "+=": "-=") + bordLeft;
  rightAnim = (( hidden[12] + even ) ? "+=": "-=") + bordRight;
  topAnim = (( hidden[12] + even ) ? "+=": "-=") + bordTop;
  bottomAnim = (( hidden[12] + even ) ? "+=": "-=") + bordBottom ;
  $box[12].animate({
    borderLeftWidth :leftAnim, borderTopWidth:topAnim, 
    borderRightWidth:rightAnim, borderBottomWidth:bottomAnim
   },{
	  duration:duration, step : function(){k++; $(this).children().text(k);}
   }
  );
 }

 var animList =[anim1,anim2,anim3,anim4,anim5,anim6,anim7,anim8,anim9,anim10,anim11,anim12];
 $.each(animList,function(i,n){ $btn[i+1].click(n) });
 $btn[13].click(function(){$.each(animList,function(i,n){n()})});
 $btn[14].click(function(){
  $.each(animList,function(i,n){setTimeout(n,400*i)})
 });
 // 乱数を使って 2 つのボックスの位置を交換する
 $btn[15].click(function(){
  var A = parseInt(Math.random()*12)+1,
    B = parseInt(Math.random()*12)+1,posA={},posB={};
  while ( A===B ){
    B = parseInt(Math.random()*12)+1;
    if (A!==B) break;
  }
  var $boxA=$("#box"+A+"_724"),$boxB=$("#box"+B+"_724");
  $boxA.animate({top:$boxB.css("top"),left:$boxB.css("left")},duration,"easeInOutBounce");
  $boxB.animate({top:$boxA.css("top"),left:$boxA.css("left")},duration,"easeInOutElastic");
 });
 // 乱数を使って 6 つのボックス位置を一瞬で入れ替える
 $btn[16].click(function(){
  var A = parseInt(Math.random()*12)+1,
   B = parseInt(Math.random()*12)+1,
   C = parseInt(Math.random()*12)+1,
   D = parseInt(Math.random()*12)+1,
   E = parseInt(Math.random()*12)+1,
   F = parseInt(Math.random()*12)+1,
   posA={},posB={},posC={},posD={},posE={},posF={};
  while ( A===B ){
   B = parseInt(Math.random()*12)+1;
   if (A!==B) break;
  }
  while ( C===A || C===B){
   C = parseInt(Math.random()*12)+1;
   if (C!==A &&C!==B) break;
  }
  while ( D===A || D==B || D===C){
   D = parseInt(Math.random()*12)+1;
   if (D!==A && D!==B && D!==C) break;
  }
  while ( E===A || E==B || E===C || E===D){
   E = parseInt(Math.random()*12)+1;
   if (E!==A && E!==B && E!==C && E!==D) break;
  }
  while ( F===A || F==B || F===C || F===D || F===E){
   F = parseInt(Math.random()*12)+1;
   if (F!==A && F!==B && F!==C && F!==D && F!==E) break;
  }
  var $boxA=$("#box"+A+"_724"), $boxB=$("#box"+B+"_724"),
   $boxC=$("#box"+C+"_724"), $boxD=$("#box"+D+"_724"),
   $boxE=$("#box"+E+"_724"), $boxF=$("#box"+F+"_724");
  $boxA.animate({top:$boxB.css("top"),left:$boxB.css("left")},duration,"easeInOutBounce");
  $boxB.animate({top:$boxA.css("top"),left:$boxA.css("left")},duration,"easeInOutElastic");
  $boxC.animate({top:$boxD.css("top"),left:$boxD.css("left")},duration,"easeInOutExpo");
  $boxD.animate({top:$boxC.css("top"),left:$boxC.css("left")},duration,"easeInOutQuart");
  $boxE.animate({top:$boxF.css("top"),left:$boxF.css("left")},duration,"easeInOutCirc");
  $boxF.animate({top:$boxE.css("top"),left:$boxE.css("left")},duration,"easeInOutBack");
 });

 // ボックス位置交換をタイムラグを持たせて行う
 $btn[17].click(function(){
  var A = parseInt(Math.random()*12)+1,
    B = parseInt(Math.random()*12)+1,
    C = parseInt(Math.random()*12)+1,
    D = parseInt(Math.random()*12)+1,
    E = parseInt(Math.random()*12)+1,
    F = parseInt(Math.random()*12)+1,
    posA={},posB={},posC={},posD={},posE={},posF={},delay=800;
  while ( A===B ){
   B = parseInt(Math.random()*12)+1;
   if (A!==B) break;
  }
  while ( C===A || C===B){
   C = parseInt(Math.random()*12)+1;
   if (C!==A &&C!==B) break;
  }
  while ( D===A || D==B || D===C){
   D = parseInt(Math.random()*12)+1;
   if (D!==A && D!==B && D!==C) break;
  }
  while ( E===A || E==B || E===C || E===D){
   E = parseInt(Math.random()*12)+1;
   if (E!==A && E!==B && E!==C && E!==D) break;
  }
  while ( F===A || F==B || F===C || F===D || F===E){
   F = parseInt(Math.random()*12)+1;
   if (F!==A && F!==B && F!==C && F!==D && F!==E) break;
  }
  var $boxA=$("#box"+A+"_724"), $boxB=$("#box"+B+"_724"),
    $boxC=$("#box"+C+"_724"), $boxD=$("#box"+D+"_724"),
    $boxE=$("#box"+E+"_724"), $boxF=$("#box"+F+"_724");
  $boxA.animate({top:$boxB.css("top"),left:$boxB.css("left")},duration,"easeInOutBounce");
  $boxB.animate({top:$boxA.css("top"),left:$boxA.css("left")},duration,"easeInOutElastic");
  setTimeout(function(){
   $boxC.animate({top:$boxD.css("top"),left:$boxD.css("left")},duration,"easeInOutExpo");
   $boxD.animate({top:$boxC.css("top"),left:$boxC.css("left")},duration,"easeInOutQuart");
  },delay);
  setTimeout(function(){
   $boxE.animate({top:$boxF.css("top"),left:$boxF.css("left")},duration,"easeInOutCirc");
   $boxF.animate({top:$boxE.css("top"),left:$boxE.css("left")},duration,"easeInOutBack");
  },delay*2);
 });

 // ボックスを整列する
 $btn[18].click(function(){
  for (var i=1; i<13; i++){
   $box[i].css({top:110*parseInt((i-1)/3)+10 +"px",left:(i-1)%3*160+10 +"px"});
  }
 });
})();

▲ToTop

jquery.js におけるアニメーションコードの解読 ( 6 )

jQuery().animate(prop, speed, easing, callback)───その 3

このエントリイでは下の模式図における色づけ部分( e.custom → e.step → e.update )、すなわち二重巡回処理内での「初期値設定、終了値設定、経過時刻取得、その時点の要素表示」という一連の流れを跡付け、愈々 「 動き 」 演出過程の核心に迫ります。

$().animate(prop,speed,easing,callback){
 $().each(func(){ || $().queue(func(){ //イテレートその1( 対象要素毎 )
  $.each(prop,func(name,val){ //イテレートその2( CSSスタイルプロパティ毎 )
■このエントリイで解読する部分
  ▼ val が toggle || show || hide 
     → e.show || e.hide メソッド起動
      これらのメソッド内で最終的に e.custom メソッドを起動
  ▲ val がその他ならば
     → e.custom メソッドを起動

  ● e.custom =初期値を取得し、アニメ開始時刻を記録する。
     ↓
     e.step =開始時刻からの経過時刻、その時点におけるCSS値などを設定する。
      ↓
      e.update =その時点のCSS値に基づいて要素を表示する。
  }); // prop 巡回function  }); // jQueryインスタンスに登録された要素を巡回するfunction };

jQuery.fx(elem, options, prop) コンストラクタと e インスタンス

引数:順にタグ要素、オブジェクト、プロパティ名称

役割:アニメーションを具体化する各種メソッドやプロパティを持つインスタンスを生成する。

このコンストラクタに new 演算子を適用して生成されるインスタンス e は、当初はコンストラクタの定義から次の 3 つのプロパティを持ちます。

 e ={
   elem : 第 1 引数, options : 第 2 引数, prop : 第 3 引数, options.orig : {}
 }

その後、各種メソッドによる e のプロパティの拡張/追加や、当該コンストラクタの prototype オブジェクトも定義されるので、e オブジェクトは最終的に次のように拡張されます。こうして e オブジェクトは現在進行中のアニメに係る各種情報を満載したオブジェクトになります。また、#4151 ~ 4171 によって、jQuery.fx クラスは 2 つのクラスプロパティを与えられます。

これらのことを模式図的にまとめると以下のようになります。

■ jQuery.fx コンストラクタの e インスタンス
 e = {
 elem:elem,// 第 1 引数
 prop:propName, // 第 3 引数
 options: {
  queue:false || true, step:function(){・・・}, // これらはユーザーが指定する。
  old,complete,easing,duration,display,overflow,// animateメソッドから引き継がれる。
  curAnim, //animateメソッドの第一引数である prop オブジェクトが格納される。
  orig:{prop},
  show:true || false || undefined, hide:true || false || undefined
 },
 startTime, start, end, unit, now, pos, state,
 // メソッド
 cur(), custuom(), step(), update(), show(), hide()
}

■ jQuery.fx コンストラクタのクラスプロパティ
 jQuery.fx.speeds = {"slow":600, "fast":200, _default:400}
 jQuery.fx.step = {opacity:fn(e){・・}, _default:fn(e){・・}}

それでは上の「 このエントリイで解読する部分 」 の模式図に従って、e インスタンスの各種メソッドを跡付けることにします。最初に登場するのは e.show() メソッドです。

▲ToTop

はじめに ─── $().show() / $().hide() と e e.show() / e.hide()

これから解読する e.show() メソッドと e.hide() メソッドは、jQuery インスタンスから起動される show() メソッド、hide() メソッドと表現が酷似しています。最初は余りに似ているこの表現に戸惑いました。

jQuery インスタンスの show / hide メソッドを引数なしで起動した場合には、displayプロパティが none 以外( show の場合 )、あるいは none( hide の場合 )に設定されて、対象タグ要素が瞬時に表示 / 隠蔽されます。

これに対して、jQuery インスタンスの show / hide メソッドをアニメ所要時間などの引数付きで起動した場合には、animate メソッドが呼び出されて、そこから更に e.show / e.hide メソッドが呼び出されます。この時 e.show / e.hide メソッドは決して display 値を none とすることはなく、常に表示モードのママ保たれます。

瞬時アニメと時間を掛けて行うアニメ動作では、時間要素の有無という根本的な差異があるため、スクリプト動作も本質的に変わります。

jQuery インスタンスの show / hide メソッドは、style.display プロパティの none と block、inline などの値を使用して表示と隠蔽を操作し、これに対して e.show / e.hide メソッドは、時間を掛けて当該タグ要素の大きさ等に係る各種 CSS プロパティ値(マージン、パディング、内容及び不透明度の他、ボーダーサイズ、フォントサイズ等々も対象となる)を操作して、要素の表示状態を変化させます。ここでは表示状態を操作するのであって、決して要素を隠蔽することはありません。

e.show() メソッド

引数:なし

返値:なし

機能:対象要素の対象 CSS プロパティ値の、初期値( 0 又は 1 )と終了値を設定し、これらを custom メソッドに渡します。こうして最小或いは透明な表示状態 =「 実質的な非表示状態 」から、表示状態へと移行するアニメーションが実現されます。

このメソッドは対象要素に対して、「実質的な」非表示状態からアニメ前にブラウザが描画していた値までのアニメーションを引き起こします。その進行は次の 3 過程に分けられます。

第 1 過程( #4070~4072 )はアニメ開始前の style.properties 値の記憶です。アニメーション開始前に、アニメ対象タグ要素 elem の elem.style.properties を、e.options.orig プロパティに待避/記憶させます。このメソッドから最終的に起動される update メソッドが、elem.style.properties を書き換えてアニメーションを実現するので、この過程が必要となります。

また、CSS プロパティ値を記憶させた後に e.options.show 値を true に設定し、e.show() メソッドが起動されたことも記憶させます。

第 2 過程( #4077 )では、対象 CSS プロパティの初期値と終了値を custom メソッドに渡します。初期値はプロパティが width または height の場合には 1 、その他の場合には 0 とし、終了値は e.cur メソッドを起動して取得します。こうして得られた初期値と終了値を引数にして custom が起動されます。

ここに対象 CSS プロパティの初期値を 1 又は 0 とするのは、言うまでもなく当該要素のアニメーションを「実質的な非表示状態」から開始するためです。

第 3 過程は $(this).show()メソッドの起動です。これにより custom メソッドが終了し、既に当該要素が表示状態になっていることを "olddisplay" という名称で当該要素に関連づけます。

4068: // Simple 'show' function
4069: show: function(){
4070:  // Remember where we started, so that we can go back to it later
     // 4077 行でスタイル属性値を 1 又は 0 ゼロにするので、現在値を記憶させておく。
     // options.orig オブジェクトの prop プロパティに
     // 当該要素のスタイル属性オブジェクトの prop プロパティの値を代入する。
4071:  this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
4072:  this.options.show = true; // e.show メソッドが起動されたことを記録する。
4073:
4074:  // Begin the animation
4075:  // Make sure that we start at a small width/height to avoid any
4076:  // flash of content
     // ちらつきを避けるために幅と高さの最小値はゼロとせず、その他のスタイル
     // 属性値は CSS 値はゼロとして start CSS 値を設定し、end CSS 値をカレント
     // スタイル値として、custom メソッドを起動する。
4077:  this.custom(this.prop == "width" || this.prop == "height" ? 1 : 0, this.cur());
4078:
4079:  // Start by showing the element
4080:  jQuery(this.elem).show(); // jQuery インスタンスメソッド show()の起動により、
4081: },// 当該要素が表示状態にある等の情報を記録させる。

▲ToTop

e.hide() メソッド

引数:なし

返値:なし

機能: e.show() メソッドの逆のことを行います。表示状態から「 実質的な 」非表示状態へと移行するアニメーションを引き起こします。

このメソッドの進行過程は単純です。最初に行うことは、e.show() メソッドと全く同一です。当初描画時の CSS プロパティ値を e.options.orig プロパティに待避/記憶させ、かつ、e.options.hide 値を true に設定し、e.hide() メソッドが起動されたことも記憶させます。(#4085~4087)

次に、 custom メソッドに対象 CSS プロパティの初期値と終了値を渡して、表示状態から隠蔽状態へ移行するアニメーションを励起します。この初期値は e.cur() メソッドを起動させて現在表示値を取得し、終了値は 0 です。(#4089~4090)

ここに、e.show メソッドの場合と異なり、非表示状態の CSS 値を全てゼロとしていることに留意する必要があります。

showメソッドの場合には、非表示状態から表示状態へ遷移する際の "ちらつき" を防止するため、プロパティ名が width と height の時にはプロパティ初期値を 1 としていましたが、e.hide メソッドの非表示状態値(=アニメ終了値)は、プロパティに関わらず全てゼロとしています。隠蔽する場合にはちらつき問題が発生しないから、全てのプロパティ値をゼロにしても支障がないのです。

それでも決して display 値は none にしていないことも同時に確認しておく必要があります。e.hide メソッドは要素を CSS 的には隠蔽せず、実質的な非表示状態で表示し続ける、と言えばよいでしょうか。

4083: // Simple 'hide' function
4084: hide: function(){
4085:  // Remember where we started, so that we can go back to it later
4086:  this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
4087:  this.options.hide = true; // this.options.hide プロパティを true とする。
4088:
4089:  // Begin the animation
     // CSS 現在値を start に、end 値をゼロとして custom メソッドを起動する。
4090:  this.custom(this.cur(), 0);
4091: },

▲ToTop

e.custom(from,to,unit) メソッド

引数:順に、初期値、終了値、単位

返値:なし

機能:初期値、アニメ開始時刻、e インスタンスのアニメ用の各種プロパティ( start、end、unit、now、pos 及び state )と 1 つの関数( t 関数 )を次々に設定し、その後 e.step() メソッドを起動して、インターバル関数によって 13 ミリ秒毎、当該要素の各プロパティ毎にプロパティ値を変化させ、もってそれを表示させてアニメーションを実現し、更に終了処理を行います。

このメソッドはその名の通り、カスタム ( slideUp や fadeOut などの既成アニメーションではない、ユーザー指定の ) アニメーションの中核を為します。その役割により 2 つのブロックに分けて考察します。

前半の過程
#4038 ~ 4050 では、現在対象としているプロパティのアニメ開始時刻、アニメ初期値、アニメ終了値、単位等を設定し、それを利用して演算する e.step メソッドを起動させる t 関数を定義します。また t 関数のプロパティとして対象要素を登録します。ここまでは非常に分かりやすい簡単なコード進行です。
後半の過程

#4050 ~ 4065 は、前半とは対照的に大変複雑です。

改めてアニメコードの全体を思い起こすと、今対象としている動作は animate メソッドの jQuery インスタンスの各要素毎の巡回処理の、その中での各 CSS プロパティ毎の巡回処理過程で引き起こされている事象です。この複雑さ故にコード進行過程が分かりにくいので、操作対象を以下のように可視化してみます。

対象としている jQuery インスタンスに登録されているタグ要素数を m 、アニメ用の CSS オブジェクトに登録されているプロパティ数を n として、或る t 関数がどの要素のどのプロパティのためのものなのか分かるように、以下のように表示します。但し、この数列に酷似した表示はあくまでも説明用です。

  • 最初の要素に対する最初のアニメプロパティ用 t 関数……e1 t p1
  • 最初の要素に対する 2 番目のアニメプロパティ用 t 関数……e1 t p2
  • j 番目の要素に対する k 番目のアニメプロパティ用 t 関数……ej t pk
  • m 番目の要素に対する n 番目のアニメプロパティ用 t 関数……em t pn

上のように各々が識別できる( m × n )個の t 関数を名付けた上で、#4052-4064 を解読します。

▲ToTop

後半の過程の詳説
  1. e1 t p1 、つまり最初の要素に対する最初のアニメプロパティに係る t 関数が 4052 行で実行されると、初めて step メソッドが起動され、その時の時刻が ローカル変数 t に記録されます。この 変数 t が custom メソッドで定義された e.startTime(s)に、animate メソッドの引数から定められた duration 時間(d)を加算した時刻( s + d )以内であれば ( つまり t ≦ s + d であれば )、step メソッドは、それ自信の中で算出するその時のプロパティ値に基づいて、対象要素の表示サイズなどを算出してから、update にそれを渡します。そして update メソッドは受け取った値からブラウザに要素を表示してから true を custom メソッドに返します。
  2. 4052 行において、t() に拠る返値が true となるので、jQuery.timers 配列の要素にその t 関数 e1 t p1 が追加され、3 番目の式 !timerId が評価されます。ここに、jquery.js トップレベルで定義されている変数 timerId は jquery.js コードの中で custom メソッドでしか使われていませんから、step メソッドから 1 つ目の true が返された時には、timerId は未定義です。つまり !timerId === true となります。こうして 4052 行の if 文が成立して、インターバル関数が起動され、以後は 13 ミリ秒ごとに t 関数が呼び出され、その返値の評価を行う行為が繰り返されます。
  3. 4052 行の if 文が成立した 13 ミリ秒後には、4056 行により timers 配列の 0 番目の要素である e1 t p1 が再度実行されます。このとき依然として 変数 t ≦ s + d ならば、step メソッド内で算出された値に基づいて、e1 要素の 2 度目の描画がおこなわれます。そして当該要素は 13 ミリ秒後に再び起動されるために配列内に残されたママとなります。他方 変数 t > s + d ならば、step メソッドが当該プロパティに係る終了処理 ( その内容については step メソッドで解説します ) を行ってから false を返すので、4057 行の if 文が成立して、e1 t p1 関数が配列から削除されます。(#4058)
    以上のような e1 t p1 の 13 ミリ秒毎の反復起動によって、e1 要素の変化が演出されます。
  4. さて、13ミリ秒毎の繰り返しと並行して、Javascript インタープリタは setInterval 関数を起動した直ぐ後に 同一要素の次のプロパティ(e1p2)を対象とした処理に進みます。3892 行の jQuery.each メソッドが 2 つめの prop を対象とした処理を行うのです。そして再び custom メソッドが起動され、その前半過程が処理され後半過程に入ってきます。今度 4052 行で起動される t 関数は e1 t p2 となり、再び jQuery.timers 配列にその t 関数 e1 t p2 が追加されます。
  5. この t 関数を追加した時点でまだ e1p1 のアニメが終わっていなければ 4052 行の if 文は成立しませんが、timers 配列には今追加したばかりの t 関数も含まれるので、13ミリ秒毎に繰り返される e1 要素のプロパティ値の変化過程が 2 つのプロパティで行われることになります。
  6. 他方、e1 t p2 関数追加時点で e1p1 に係るアニメが終わっていれば、4062 行により timerId は未定義となっているので、4052 行の!timerId が成立して、再びインターバル関数が 13 ミリ秒毎の t 関数実行を開始します。こうして最初の要素の 2 つ目のアニメプロパティに係る動きが演出されていきます。
  7. 以上のような過程が、対象とした jQuery インスタンスに格納されている m 個の要素毎の、それぞれ n 個のアニメプロパティにおいて繰り返されます。そして n × m 回の custom - step - update の連鎖起動の反復繰り返しを経て、遂に、対象とした jQuery インスタンスから起動された animate メソッドはその役割を終えます。
4036: // Start an animation from one number to another
4037: custom: function(from, to, unit){
4038:  this.startTime = now(); // 現在時刻を登録する。
4039:  this.start = from; // アニメ開始値
4040:  this.end = to; // アニメ終了値
4041:  this.unit = unit || this.unit || "px";
4042:  this.now = this.start; // = from となる。
4043:  this.pos = this.state = 0;
4044:
4045:  var self = this; // this は jQuery.fx クラスのインスタンス
4046:  function t(gotoEnd){ // t 関数を定義する
4047:   return self.step(gotoEnd); // step メソッドの実行結果を返す。
4048:  }
4049:
4050:  t.elem = this.elem; // t の elem プロパティ値に、this.elem を代入する。
4051:  // t 関数の実行結果が true で、t 関数を追加した timers 配列が存在し、
     // かつ timerId が存在しなければ
4052:  if ( t() && jQuery.timers.push(t) && !timerId ) {
4053:   timerId = setInterval(function(){
4054:    var timers = jQuery.timers;
4055:
4056:    for ( var i = 0; i < timers.length; i++ )
4057:     if ( !timers[i]() ) // timers[i] の実行結果が false ならば
4058:      timers.splice(i--, 1); // i 番目の要素を削除してから i を 1 減じる。
4059:
4060:    if ( !timers.length ) { // timers 配列の要素がなくなれば
4061:     clearInterval( timerId ); // timerId を停止し、
4062:     timerId = undefined; // timerId を未定義とする。
4063:    }
4064:   }, 13);
4065:  }
4066: },

▲ToTop

e.cur(force) メソッド

引数:強制的に算出スタイル値の取得を行う場合 true とする。

返値:スタイル値(タグ属性に記述されたスタイル値、又はスタイルオブジェクトで設定されているスタイル値、又は算出スタイル値)

機能:返値の説明の通り。

e.cur メソッドは、animate、e.show、e.hide などの各種メソッドから呼び出されて、タグ属性や style オブジェクトに設定されているスタイル値を取得し、あるいは指定があれば算出スタイル値を取得します。

まず、animate メソッドからは引数 true 付きで呼び出されます。(#3899)true 付きなので対象要素のアニメ開始前の算出スタイル値が強制的に取得され、その値がアニメ開始値とされます。

他方、e.show メソッド及び e.hide メソッドからは引数なしで呼び出されるので(#4077)、アニメ開始前の対象要素のタグ属性値又は style オブジェクト値が取得されて、アニメ終了値となります。

4027: // Get the current size
4028: cur: function(force){
     // 要素属性に当該プロパティ値が存在し、かつスタイル属性がないか、
     // あっても当該プロパティ値がない場合には
4029:  if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) )
4030:   return this.elem[ this.prop ]; // 要素属性値を返す。
4031:  // 当該要素の当該プロパティ値を単位名なしで取得する。
4032:  var r = parseFloat(jQuery.css(this.elem, this.prop, force));
     // r がゼロ以外で -1 万よりも大きければ r を返す。
     // さもなければ force 引数なしで当該要素の当該プロパティの CSS 値を取得する。
     // 取得できなければ 0 を返す。
4033:  return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
4034: },

▲ToTop

e.step(gotoEnd) メソッド

引数:gotoEnd を true に設定すると対象要素のアニメーションを停止させます

返値:false(アニメが終了した時) 又は true(アニメ実行中の時)

機能:2つの役割があり、第 1 はアニメ開始とアニメ終了の始点/終点間のプロパティ値を経過時間に基づいて刻むことです。この場合、返値 true を e.custom メソッドに返します。第 2 は、アニメ終了後の処理を行うことです。この場合返値 false を e.custom メソッドに返します。

このメソッドを理解する前提として、呼び出し元の e.custom メソッド及びその呼び出し元の animate メソッドによって、e.options オブジェクトの各種プロパティが既に拡張されていること、そしてそれらが e.step メソッドで縦横に利用されることを踏まえておく必要があります。

step メソッドはまず最初に、このメソッドが呼び出された時刻を変数 t に記録します。最初に行うこの作業は、時間という 「 変数 」 を管理し、要素の描画を自在に操作する上で不可欠です。

アニメーションを刻む場合

変数 t の値 ( step メソッドの起動時刻 ) が、e.custom メソッドが起動された時刻 ( startTime ) と e,options.duration の合算値よりも小さければ、すなわち、アニメが起動してからまだ duration で指定されている時間が経過しきらない内に step メソッドが起動された場合には、e.step メソッドはアニメーションを刻むためのプロパティ値の設定を以下のように行います。( #4134 ~ 4144 )

  1. #4135 の変数 n は経過時間です。
  2. #4136 の e. state は動作状態変数 ( 「 動作ステータス 」とも呼ばれるらしい )と呼ぶべきもので、duration 時間を 1 としたときの経過時間の割合を表します。0 ~ 1 未満の値となります。例えば e.state == 0.5 ならば ( その値は options.step メソッドによって取り出さない限りユーザーには見えませんが ) アニメが半分進行したその瞬間を表すことになります。
  3. #4139 の e.pos には n 、e.state 及び e.options.duration の 3 つの値から easing 関数を使用して、その時点における変化率を算出した結果が代入されます。なお、easing 関数は、(1)ユーザー指定のもの、(2)それがなければ jquery.js で定義されている swing、(3)何らかの理由によりそれも定義されていなければ linear が使用されます。裏返せば default easing は swingです。
  4. #4140 の e.now には直前で取得した変化率に、アニメによる変化量( 終了値 -開始値 ) を乗じて変化値を算出し、それを開始値に加算した結果が代入されます。
    こうして、その state におけるプロパティ値が得られます。
  5. 最後に #4143 にて e.update メソッドによりブラウザ表示を更新してアニメーションの刻みが終わります。

▲ToTop

アニメーションの終了処理を行う場合

(1) e.step の引数が true の場合、または (2) 変数 t の値 ( step メソッドの起動時刻 ) が、e.custom メソッドが起動された時刻 ( startTime ) と e,options.duration の合算値以上の場合には、すなわち、アニメ停止が指示されたか、またはアニメが起動してから既に duration 時間が経過してしまった時点で step メソッドが起動された場合には、e.step メソッドはアニメーション終了後の一連の終了処理を行います。( #4097 ~ 4134 )

  1. (#4098)その段階ではプロパティ値 e.now は e.end ( 既に設定済みの終了値 ) とします。
  2. (#4099)変化率 e.pos も 状態変数 e.state も共に 1 とすることによって、アニメーション進行中に既にこれらに代入されていた別の値 ( 共に 0 以上で 1 より小さい値 ) を上書きし初期化します。
  3. (#4100)以上の値によりブラウザ表示を更新します。これでアニメーションの最終形が描かれます。
  4. 次に、アニメーション終了処理、すなわちプロパティ値をアニメ開始前の値へ戻す初期化作業を行います。
    1. (#4102)まず、animete メソッドによって e.options.curAnim に登録済みの、今対象としているプロパティの値を true とします。これはそのプロパティのアニメーションが終わったことを意味する flag です。
    2. (#4104 ~ 4107)次に、ローカル変数 done を用意して値を true とします。この変数は次の for ループコードとセットになって、animate メソッドの第 1 引数で指定した prop オブジェクトの全てについて、それぞれがアニメーションを終えたかどうかが検証するために使われます。

      ひとつでもアニメーションを終えていないプロパティがあれば done は false となり、#4109 ~ 4131 は実行されないまま #4133 の false が e.custom メソッドに return されます。後処理を施さないまま step メソッドを終えてしまうわけです。( ※ これで果たして良いのか、疑問があるのですが解消出来ていません。)

    3. (#4110 ~ 4131)他方、上の #4104 ~ 4107 において全てのプロパティのアニメーション終了が確認されれば、つまり done が true のままならば、#4109 の if 文が成立してそれに続くブロックが実行されます。このブロックではアニメの対象となったプロパティを、アニメ開始前のプロパティ値に復元する ( 初期化すると言っても良い ) 作業が行なわれます。
      1. (#4110 ~ 4118)まず、e.options.display 値が空ではない場合の処理です。元々この値は animete メソッドの #3880 においてアニメ開始前の状態から待避/取得されています。もし、style.display があればその値が代入され、なければ算出スタイル値が代入されています。また、null 値以外の値とは、block、inline、inherit、none など display プロパティが取り得る値を指します。

        #4112 ではアニメ開始前に animate メソッドによって e.options.overflow に待避しておいた初期値を、対象要素の style.overflow プロパティに戻して復元します。

        次に、#4115 では、e.options.display に待避しておいた値を対象要素の style.display プロパティに戻します。その上で、#4116 ~ 4117 では、対象要素の display プロパティ値が "none" ならば ( null ではなく "none" であることに注意!! )、対象要素の style.display プロパティを "block" に書き換えます。
        この最後の処理は非表示になっている要素を強制的に「一旦」表示することになります。

      2. (#4121 ~ 4122)ここで先の「一旦」と記した意味が明らかになります。もし animate メソッドにおいて e.hide メソッドの起動があった場合には、当該要素を隠蔽します。どうして「一旦 block 表示させておき、その直後に隠蔽する」という煩わしい処理を行う必要があるのか───それは未解明です。
      3. (#4125 ~ 4127)animate メソッドにおいて e.show または e.hide メソッドが起動されていれば、e.options.curAnim オブジェクトに待避させておいた animate メソッドの第 1 引数のプロパティを巡回走査して、対象要素の style オブジェクトの当該プロパティ値を、e.options.orig オブジェクトの同一プロパティ名に格納しておいた値で上書きします。ここに e.options.orig オブジェクトには、e.show や e.hide メソッドによって、対象要素の style オブジェクトのアニメ前の当該プロパティ値が待避されています。───このような複雑な工程がどうして必要なのか未解明です。
  5. (#4130)最後に animate メソッドにおいてアニメ終了後に起動することを指定しておいた関数等を起動します。
■ e.step() メソッドコード
4093: // Each step of an animation
4094: step: function(gotoEnd){
4095:  var t = now(); // 現在時刻を代入する。
4096:  // gotoEnd があるか、t が所定時間よりも大きい場合には
4097:  if ( gotoEnd || t >= this.options.duration + this.startTime ) {
4098:   this.now = this.end;
4099:   this.pos = this.state = 1;
4100:   this.update(); // 要素のカレントサイズを測る。
4101:   // curAnim の当該プロパティ値を true とする。
4102:   this.options.curAnim[ this.prop ] = true;
4103:
4104:   var done = true;
4105:   for ( var i in this.options.curAnim )
4106:    if ( this.options.curAnim[i] !== true )
4107:     done = false; // trueでなければ
4108:
4109:   if ( done ) { // done===true ならば
4110:    if ( this.options.display != null ) { // display が null 値でないならば
4111:     // Reset the overflow
        // options の overflow 値を要素の overflow style 属性に代入する。
4112:     this.elem.style.overflow = this.options.overflow;
4113:
4114:     // Reset the display
        // options の display 値を要素の display style 属性に代入する。
4115:     this.elem.style.display = this.options.display;
        // display 値が none ならば
4116:     if ( jQuery.css(this.elem, "display") == "none" )
4117:      this.elem.style.display = "block"; // block とする。
4118:    }
4119:
4120:    // Hide the element if the "hide" operation was done
4121:    if ( this.options.hide ) // hide プロパティがあれば
4122:     jQuery(this.elem).hide(); // 隠す。
4123:
4124:    // Reset the properties, if the item has been hidden or shown
       // hide 又は show プロパティがあれば
4125:    if ( this.options.hide || this.options.show )
4126:     for ( var p in this.options.curAnim ) // curAnim オブジェクトを巡回走査
         // style 属性オブジェクトの p プロパティの値を options.orig[p] とする。
4127:      jQuery.attr(this.elem.style, p, this.options.orig[p]);
4128:     
4129:    // Execute the complete function
4130:    this.options.complete.call( this.elem ); // complete 関数を実行する。
4131:   }
4132:
4133:   return false; // false を返す。
4134:  } else {// 引数がないか、t が所定時間以下の場合には
4135:   var n = t - this.startTime; // 現在時刻とアニメ開始時刻との差を n に代入。
      // n を継続時間で除した値を state に代入。state は 1 未満の値となる。
4136:   this.state = n / this.options.duration;
4137:
4138:   // Perform the easing function, defaults to swing
      // easing 関数を実行する。それが指定されていなければ swing 関数を実行する。
      // その時の引数は順に this.state, n, 0, 1, this.options.duration
4139:   this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
      // 終了点から開始点を差し引いてから、easing 計算値を乗じた値を開始点に加算する。
4140:   this.now = this.start + ((this.end - this.start) * this.pos);
4141:
4142:   // Perform the next step of the animation
4143:   this.update(); // 次の段階へ進む。
4144:  }
4145:
4146:  return true; // true を返す。
4147: }

▲ToTop

e.update() メソッド

引数:なし

返値:なし

機能:アニメが進行している要素のブラウザ表示を更新します。

このメソッドは animate メソッドから呼び出される一連のメソッドの最後に位置しています。名前の通り表示を更新するメソッドですが、大切なことはこのメソッドのどこにも display = none が存在しないことです。

このメソッドは、step メソッドから時々刻々伝達される、遷移しつつある大きさや不透明度の値を受け取り、それに従って要素の表示状態を更新する役目を負っているのであって、例え各種プロパティの表示サイズがゼロとなり、不透明度がゼロとなった場合でも、display 値を block 、inline などの ( none ではない ) 表示用の値に設定したままで機能します。決して none にはしません。このことをしっかり踏まえなければ成りません。

  1. (#4017) e.options.step オブジェクトが存在すれば、e.now と e を引数にして、アニメ対象要素からそれを起動します。

    animate メソッドの第 4 引数や completeプロパティに指定する関数が、アニメーションが終わってから起動されるのに対して、この e.options.step は、アニメションの途中で何かを行うためにユーザーが指定する関数です。進行中のアニメーションの状態変数 e.state が変化する度に、何かを行わせたい時に使用します。

    なお、このメソッドの指定方法は queue プロパティと同様に speed オブジェクトのプロパティとして { queue:false, step:function(){}, duration:duration,・・・・} のようになります。この step 関数を使えば、アニメ中の各状態毎に「アニメ進捗状況に関する情報を表示する」、「背景色や文字色を変える」などが可能となります。とは言え色替えはとても推奨できません。変わる度にディスプレイ画面がちらつくからです。

  2. #4151 ~ 4171 によって jquery.js 読み込み時に speed と step プロパティが jQuery.fx オブジェクトに追加されます。 #4020 はそれを呼び出して使用する箇所です。

    ここに、jQuery.fx オブジェクトは #4001 ~ 4009 において定義されているコンストラクタ関数オブジェクトで、そのインスタンス e を縦横に駆使してアニメーションが実現されていることはこれまで見てきたとおりです。

    では jQuery.fx オブジェクトに追加された speed と step の 2 つのプロパティと、インスタンス e との関係はどうなるのか、直ぐに疑問が湧いてきます。

    答えは簡単で、e インスタンスにはこれらのプロパティは登録されません。これらがいわゆるクラスプロパティだからです。

    一寸脱線しましたが、 #4020 では、e.step メソッドから渡された値を基に、この jQuery.fx オブジェクトのクラスプロパティを利用してブラウザ上で要素を表示します。

    jQuery.fx.step クラスプロパティには、#4158 ~ 4170 により 2 つのメソッドが登録されています。jQuery.fx.step.opcity() と jQuery.fx.step._default() です。#4020 における引数 this が this の仕様から呼び出し元の e インスタンスを指していることを踏まえれば、4020行 は決して難解ではありません。但し、インスタンスメソッドである e.step とは全く別物であることに気をつけなければなりませんが...。

    前半の jQuery.fx.step[this.prop] は、アニメ対象プロパティ( this.prop )が opacity の時だけ true となり、jQuery.fx.step.opacity(this) を実行します。その結果、#4160 ~4162 によってアニメ対象要素の style オブジェクトの opacity プロパティが、fx.now となり、現在値の不透明度でアニメ対象要素が描画されます。( elem をアニメ対象要素として、elem.style.opacity = fx.now となります。)

    他方、e.step メソッドから渡されたアニメ対象プロパティ( this.prop )が opacity でない場合には、後半部分の jQuery.fx.step._default(this) が実行されます。_default() メソッドは、アニメ対象要素に style 属性が存在し、しかも style[ this.prop ] が空でない場合には、 style[ this.prop ] に単位付きで fx.now を代入します。

    また、アニメ対象要素に style 属性が存在しない場合、あるいは存在しても style[ this.prop ] が空の場合には、当該要素の this.prop 属性値を単位なしで fx.now とします。

    文章化するとわかりにくいのですが、アニメ対象要素が width の場合を例示すれば、前者は elem.style.width = fx.now+"px" であり、後者は elem.width = fx.now ということです。

  3. update メソッドの最後では(#4022 ~ 4025)、アニメ対象プロパティが height または width で、かつ、アニメ対象要素にスタイルオブジェクトが存在すれば、その display 値を block とします。

    このことの意味は、inline ではなく block にするということであり、決して none を block に変えることではありません。最初に触れたように、update メソッドは決してdisplay 値を none にすることはありません。飽くまでも「 表示 」を更新する役割を担っているからです。

4015: // Simple function for setting a style value
4016: update: function(){
     // options に step プロパティがあれば this.now と this を引数として
     // step を起動する。ここに step はユーザーが独自に指定するメソッドである。
4017:  if ( this.options.step )
4018:   this.options.step.call( this.elem, this.now, this );
4019:  // this を引数として jQuery.fx.step[this.prop] 関数
     // 又は jQuery.fx.step._default関数を実行する。
4020:  (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
4021:  // アニメ対象プロパティが height または width であって、
     // かつ、アニメ対象要素にスタイルオブジェクトが存在すれば
4022:  // Set display property to block for height/width animations
4023:  if ( ( this.prop == "height" || this.prop == "width" ) && this.elem.style )
4024:   this.elem.style.display = "block"; // style.display を "block" とする。
4025: },

▲ToTop

 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が開きます。