01 | 2017/03 |  03

  1. 無料サーバー

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

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


WIndows7 インストール直後メモ(1) スリープからの自動復帰を止めさせた

1/13 追記:イベントビューアーからも原因が特定出来た

以下に述べるように、手作業的試行錯誤の結果スリープ状態からの自動・強制復帰問題は解決したが、或るサイト(こちら:Windows7の勝手にスリープから復帰・起動する問題その3(解決糸口の探り方編))にイベントビューアーで原因を特定する方法が述べられていたので、それを参考に早速ビューアーでチェックしてみた。

その結果、見事に( って当たり前だが(苦笑) )以下で特定した問題が原因であることが再確認された。

そもそも、最初からこの方法で原因を特定すれば苦労はなかった。職場のPCでスリープに入れない問題が発生しているので、早速イベントビューアで原因を特定してみるつもりだ。

ハイブリッドスリープは結構な機能だが...

Vista で2年余り使い続けた PC の OS を、この正月休みに 7 に新規アップグレードインストールしたことは前のエントリイで触れた。

このエントリイでは、各種アプリのインストールが約7割方終わり、愈々各種アプリケーションを使い始めたその直後から、困り果てていた或る問題が解決したので、その顛末を綴っておきたい。

問題とは、スリープに入れないことだ。今回それが、「一応、さしあたり」解決したのである。

ハイブリッドスリープは 7 で初めて導入された機能だ。それはスリープと休止状態を兼ねた、それ故にハイブリッドなスリープ、またはハイブリッドな休止状態である。しかし、これに対応していないハードが結構存在していて、それが混乱の元となっているのだ。

【例1】
例えば、職場のカードリーダー/ライター(日立製)の場合(これは個人認証に使っているため、業務上使用しないわけにはいかない代物だ)、スリープには入れるものの、眠りから覚ますことが出来ない。PC を覚醒させるには、当該カード読取/書込装置を USB ポートから物理的に取り外してから覚醒操作をするか、数秒電源ボタンを押し続けてから強制シャットダウンして、通常の電源オン操作を行って再起動するしかない。

【例2】
自宅の PC の場合、結論を言えばネットワークアダプタの電源遠隔操作機能を停止させないと、スリープに一旦入るものの、10 秒ほどでスリープから自動的・強制的に復帰してしまうことを止めることが出来なかった。

なお、遠隔操作を停止した操作説明図は以下の通り

deviceManager deviceManager

2 番目の図の「このデバイスで、コンピューターのスタンバイ状態を解除出来るようにする」をオフにしたのだ。その後ハイブリッドスリープに入らせてみたところ、これまでのように強制的・自動的に目覚めることはなく、所定通り、キーボード操作によって PC をハイブリッドな眠りから目覚めさせることに成功したのだ。(後述するように、マウス操作によってはスリープから復帰出来ないように、デバイスドライバ設定を変更した。)

▲ToTop

原因究明に"あくせく"

ネット検索を掛けた結果、usb ハードディスクとの相性問題や、電源タップ上での電源コードの接続問題(連動機能のあるタップかどうか)など、物理的な問題が原因と思われたので、タップを取り替えたり、差し込む位置を変えたり、usb ハードディスクを全て切り離してみたり、色々と試してみた。しかし、それらの全てにおいて、スリープ状態からの 10 秒程度での自動的・強制的な復帰を止めることが出来なかった。

昨晩と今晩、合計 2 時間以上要して試行錯誤したが、結局解決策を見いだすことが出来ないまま、時が流れたのだ。

そこで、どうにも分からないので更に検索を掛けたところ、デバイスドライバを弄って、各デバイスからの電源管理機能を変化させることによって、スリープ問題から脱出出来た、との情報を入手出来た。それがこれだ。→ MacBook (Pro)でWindows Vistaのスリープ時のトラブルを回避するには - パソコンよろずQ&A

このサイトの中程に、「スリープに移行できない・勝手に復帰してしまう問題を解消するには」なる項目があり、そこでデバイスマネージャ上で、デバイスの電源管理機能を変更して解決した例が解説されていた。

即座に「これだ! 」と感づき、怪しいデバイスを全てチェックし、1つずつ影響を調べてみることにした。

▲ToTop

電源管理を行えるデバイスはかなり多く存在しているが...

実施に影響を与えていると思われるものは殆どなく、デバイスドライバ一覧のチェック開始から数分後に辿り着いた犯人とおぼしきもの、それがネットワークアダプターだった。

それにも電源管理タブがあり遠隔操作が可能となっていた。直感的に、これが原因かもしれない、2年半前に購入した PC なのでハード的に、あるいは BIOS が、ハイブリッドなスリープには対応していないのかもしれない。遠隔操作( Wake On 操作)は全くしないのだから、デバイスのこの機能を止めても何の支障もない。ならば、とりあえず、さしあたり遠隔で可能となる Wake On 機能を止めてみよう!───と判断したのだ。

そして実際、ネットワークアダプタの Wake On 機能を停止したところ、全く問題なく眠りには入れたし、自動的/強制的に目覚めてしまうこともなくなったのだ。

以上、試行錯誤の結果ハイブリッドスリープのエラーから脱出出来た、簡単なメモである。

付記:マウスからは復帰出来ないようにした

マウス操作によってスリープ状態から復帰出来てしまうと困ることがある。

物が一寸マウスに触れただけで、あるいはマウスを一寸動かしただけで、PCの眠りを覚ましてしまうことがママあるからである。起動するつもりはなかったのにマウスが動いたためPCが起動してしまった、と言う事例には事欠かないのではないかと思う。

だからデバイスドライバのチェック序でに、マウスによる電源管理機能をオフにしたのである。その結果電源ボタン以外はキーボードからのみ復帰が行えるように改善されたのだ。

信じ難い IE の Javascript インタープリタの挙動

animatedPopup も makeTableContents も IE では動かない。

その他各種の jQuery plugin を作ってきたが、それらの多くが IE で動かない原因をずっと解明できないまま、時は空しく流れ去るばかりだった。IE 用にはお粗末なデバッガーしかないので、動かない原因がなかなか特定出来ないでいたのだ。

しかし、未だに相変わらずトップシェアを占めている IE で、目次作成プラグインなど全てのエントリイにおいて活用させたいものが動かないのは、ブログ製作のあり方として耐え難い。そこで、Firebug Lite や IE 8 の開発者ツール、あるいは DebugBar を駆使して、前から見当を付いていた " 怪しげな箇所 " を部分に細分化し、時には一行ずつ、場合によっては一文ずつ、一式ずつテストしてみた。

そしてやっとのことで、余りに<お粗末な> IE 固有のエラー発生原因の いくつかが究明できた。以下そのお粗末さを明らかにしておきたい。

なお、今回特定した問題はいちがいにバグとは言い切れないかもしれない。

余りにお粗末なインタープリタ

それは「 論理積演算子 」&& に関わる。この演算子のオペランドは左右とも「式」が許されているのに、IE では左側のオペランドが特定の或る式の場合には、「 式なのにエンドマークの;がないからエラー! 」 と判断するのである!

例えばこうだ。

 1: $("body").mousemove(function(e){
 2:   if (e.pageX-$(window).scrollLeft()<10 && $contents.is(":hidden")){
 3:     direction = false;
 4:     displayJumpList(e.pageY);
 5:   }
 6: });
 7: $(window).scroll(function(){
 8:   if (!$contents.is(":hidden") && Math.abs(initScrollTop-$(window).scrollTop())>20){
 9:     slideFuncOut.call($contents);
10:     initScrollTop = $(window).scrollTop();
11:   }
12: });

上のコードの 2 行目と 8 行目を比較すると、前者では&&演算子の左オペランドは比較式で、右オペランドは jQuery インスタンスメソッド、つまり関数呼び出し式である。他方、後者ではそれが逆となっており、左側に jQuery インスタンスメソッドがあり、右側には比較式がある。

さて、IE で、これらの 2 つの if 文のどちらが正常に働き、どちらがエラーとして認識されてしまうか、お分かりになるだろうか??

結論を言えば、Firefox、chrome、safari (いずれも windows 版)ではいずれの行も問題なく作動するし、IE に最も近い Opera でさえ問題はない。しかし、独り IE だけが 8 行目の && 演算子の左側オペランドにセミコロンがないからエラーとして評価してしまうのだ。

『Javascript 第 5 版』によれば「 関数呼び出しは厳密には式であるが、Web ブラウザを制御する副作用があるので文の仲間に入る 」。

つまり、IE のインタープリタは、「 関数呼び出しは厳密には式であるが、副作用を伴う文の仲間に入るからセミコロンが必要だ 」と判断するわけだ。

一方他のブラウザのインタープリタは、関数呼び出しは副作用を伴うが厳密には式なので、セミコロンは求めない、ということになる。

お粗末なインタープリタに対する 1 つのささやかな対策

2 行目は問題なく作動することが確認されている。しかし 8 行目はエラー。───ではオペランドの順番を変えてみたらどうか?───こう思い付くまでにさほどの時間は掛らなかった。

2 行目に準じて 8 行目の && 演算子のオペランドの左右を次のように入れ替えるのである。

8: if (Math.abs(initScrollTop-$(window).scrollTop())>20 && !$contents.is(":hidden")){

つまり、左オペランドには比較式を置き、jQuery インスタンスメソッド呼び出し式は右オペランドに移すのだ。このように入れ替えてから、開発者ツール等で試したところ、エラーはあっさり消え去り、問題は解決してしまったのだ。

構文ミスでも、記述ミスでもないこのような記述を、敢えて「 文だからエラー 」として認識してしまうとは、余りにお粗末ではないか。

因みに、次のように括弧で括って明確に式であることを明示してみたところ、これもエラーにはならなかった。しかしこの方法は可読性が低下するので決して好ましい方法ではないだろう。&& 演算子が登場する度に、関数呼び出しオペランド部分を一々括弧で括るなんて、余りに手間が掛りすぎる!

8: if ( (!$contents.is(":hidden")) && Math.abs(initScrollTop-$(window).scrollTop())>20 ){

2 つ目は mousemove メソッドの呼び出し元に関する問題である

最終的には 2 行目の mousemove メソッド呼び出し元は、body 要素としたが、コード作成時から最終改訂の直前までは、window としていた。そして $("window").mousemove(function(e){・・・}) で IE 以外のブラウザは意図を解釈してくれた。しかし IE だけはうんとも寸とも言わないのだ。

確かに、マウスは body 要素の上、あるいは中で動くのだから、mousemove イベントの呼び出し元は body であるべきだろう。それが論理的である。しかし、マウスは window の上あるいは中で動いていることもまた真なのだから、window が起動元であっても mousemove メソッドは作用すべきだと思われる。

独自仕様を乱発してきた IE はその裏で余りに厳格な解釈を行っている

上の 2 つの事例から言えることは、そういうことである。余りに厳格な解釈によりエラーを乱発するのだ。「 仕様だから仕方がない 」という立場もあるかもしれないが、インタープリタは運用上のスマートさも兼ね備えるべきだ。

次に想起されることはこうだ─── || 演算子は IE で正常に働くのだろうか?

こちらは<予想を裏切り>、他のブラウザと同様に正常に機能した。

8: if (!$contents.is(":hidden") || Math.abs(initScrollTop-$(window).scrollTop())>20){

上の 1 行では&&演算子の左側オペランドは関数呼び出し文であるが、実行してもエラーははき出されないのだ。このことから言えることは、「 IE のインタープリタには一貫性がない 」ということだ。

余りに厳格な解釈を行うと思えば、論理的一貫性を欠く。───それが IE の Javascript インタープリタなのだ!

【 結論 】&& 演算子における IE 対策

  1. && 演算子の左側オペランドに関数呼び出し文を置かないようにする。
  2. どうしても左側に関数呼び出しを置かざるを得ない場合には、&&演算子の左側オペランドを一括りの ( ) で括って、IE が文としての厳格な解釈を適用しないようにするのが得策だ。

ブログ頁内ジャンプ移動に際して、ジャンプ先からジャンプ元に直ぐに戻れるようにする jQuery プラグイン

このエントリイの後に、別のもっとクールな 目次自動作成プラグイン を作りました。

このエントリでは 2 通りの目次が作成され、表示されるようにしましたが、それはここで述べているプラグインが、旧バージョンの目次作成プラグインを前提に作成されているからです。

なお、新しい目次自動作成プラグインについては、こちらで 述べています。

ここで作成した backNforth プラグインは animatedPopup プラグインの使用を前提にしていますが、その animatedPopup プラグインは恐縮ですが IE では動きません。原因は jquery.js ver 1.4.2 の CSS スタイルに係る部分に、IE に対してバグとなってしまう箇所があるようです。この問題は ver 1.4.4 でも解決していないようです。

長文 Web サイト閲覧で困ること

エントリイ情報が多い場合スクロールも大変であるとともに、目次があってそこからそれぞれのアイテム箇所にジャンプできるとしても、ジャンプ後に再び目次に戻りたい場合があります。

その場合には「目次に戻る」ジャンプ機能を搭載すればよいわけですが、他にも直前にジャンプした箇所に戻りたいこともあります。

つまり、あちこちの箇所に容易に行ったり来たりすることが出来れば、閲覧に際して目的の箇所に簡単に辿り着けることになります。

そこでそのようなことを可能とする jQuery プラグインを作ってみました。

当面の運用に際して、次の5つのジャンプ先がリスト表示されて、目的の箇所名をクリックすれば、直ぐにその箇所が表示されるようにしました。

その 5 つのジャンプ先とは、ページトップ、ページエンド、直前の箇所、その前の箇所、最初の箇所です。

▲ToTop

頁内移動を容易にするために

上記 5 つのジャンプ先に簡単に移動するためには、それらがダイアログ内にリスト表示され、目的箇所名をクリックすれば直ぐにそこにジャンプするようにすればよいと考え、そのリストダイアログをポップアップ表示することにしました。

ポップアップは、目次などジャンプする為に使用する箇所に起動イベントを仕込み、ジャンプした後にその画面内のトップ位置にリストダイアログが表示されるようにしました。

ジャンプ先ダイアログをアニメーション表示/隠蔽する

ダイアログは animatedPopup プラグインを使用して出現を演出しました。

しかも、そのポップアップは二段階にして、最初は余り露骨ではなく控えめに表示し、ジャンプダイアログを使いたくなければ直ぐに閉じられるようにしました。

控えめなポップアップからリストダイアログ表示への移行を容易にするために、単純にcloseBar 以外の箇所にマウスオーバーすれば良いようにしました。

何はさておき実例を

  1. このページの上の方にスクロールして、目次を表示してください。
  2. その中の適当な行をクリックすると頁内の該当箇所にジャンプします。
  3. すると移動直後に画面上部中央に小さなポップアップが表示されます。
  4. その closeBar 以外の箇所にマウスオーバーすると、ジャンプダイアログがアニメートポップアップされますので、その 5 つのリスト内から適当な箇所をクリックすれば、目的の箇所に移動します。
  5. ジャンプダイアログにはスクロールイベントハンドラーを組み込み、移動後も常にページ上部中央部に表示され続けるようにしたので、次のジャンプも容易に行えます。

スクリプトコード

  1:(function($){
  2:$.fn.backNforth = function(opts){
    /* 頁内リンク移動を行うポップアップを表示し、容易にジャンプ元に戻ったり
     * 頁トップ、頁ボトムなどへの移動をポップアップ内から指定出来るメソッドで、
     * 全面的に animatedPopup プラグインメソッドを利用している。
     * 当該メソッドの起動元インスタンスは、任意のジャンプ元要素(複数可) とする。
     * 特徴的なことは、href 属性に基づくリンク先移動のためのクリックイベントと
     * 当該プラグインメソッドを起動するイベントが、同じクリックイベントであること。
    * 勿論、他のイベントでも当該メソッドを起動出来るがクリックが一般的だろう。
     * 引数はオブジェクト形式で指定する。ポップアップ内に表示するジャンプ先情報と、
     * ポップアップの現れ方 (0~8)を指定する。
     * 但しジャンプ先情報はスクリプト本体を書き換える必要があるので、一般には指定する必要がなく、
     * 既定値と異なるジャンプ先を表示したい場合には、コードの書き換えが必要となる。
     * release:2010/11/12
     */
  3:  var jQ=this; if (jQ.size()<1) (function(){return false})();
  4:  var jumpList=[], o, $animPopup,$destination,guidePos;
  5:  if (opts==="?" || opts==="?"){
  6:    var mes = "■backNforth プラグインの引数指定方法■\n";
  7:    mes +="{ guidePos : num }\nnum は頁内移動 guide の表示位置を意味し、\n0~8 の数値にする。\n";
  8:    mes +="0:center of window,1:leftTop,2:rightTop,3:rightBottom\n";
  9:    mes +="4:leftBottom,5:topEdge,6:rightEdge,7:bottomEdge,8:leftEdge";
 10:    alert(mes);return;
 11:  }
 12:  o = $.extend({}, $.fn.backNforth.opts, opts && typeof opts==="object" && opts.constructor === Object && opts || {});
 13:  var errFunc = function(){
 14:    if (o.guidePos <1 || o.guidePos >8){
 15:      alert("guidePos の値が不正です。\nguidePosは 0~8 の数値にしてください。"); return;
 16:    }
 17:  }
 18:  var setGuidePos = function(num){
 19:    var obj = num === 0 ? {left:"center",top:"center"} :
 20:      num === 1 ? {left:"0px",top:"0px"} :
 21:      num === 2 ? {right:"0px",top:"0px"} :
 22:      num === 3 ? {right:"0px",bottom:"0px"} :
 23:      num === 4 ? {left:"0px",bottom:"0px"} :
 24:      num === 5 ? {left:"center",top:"0px"} :
 25:      num === 6 ? {right:"0px",top:"center"} :
 26:      num === 7 ? {left:"center",bottom:"0px"} : {left:"0px",top:"center"};
 27:    return $.extend({},obj,{opacity:0.8,origin:num}); //若干透明にする
 28:  }
 29:  var displayJumplist = function(){
 30:    $(this).animatedPopup(o.moveStr,{
 31:      popupCSS:$.extend({},this.guidePos,{textAlign:"left",cursor:"pointer",opacity:1}),duration:200,easing:"swing"}
 32:    );
 33:    $animPopup.children().eq(0).children().each(function(i){
 34:      var movePos = i===0 ? 0 :
 35:        i===1 ? jumpList[0] && parseInt(jumpList[0].top) || null:
 36:        i===2 ? jumpList[1] && parseInt(jumpList[1].top) || null:
 37:        i===3 ? jumpList[2] && parseInt(jumpList[2].top) || null:
 38:        i===4 ? $(document).height() : null;
 39:      $(this).click(function(e){
 40:        jumpList[2] = jumpList[3];
 41:        $("html,body").animate(
              {scrollTop:movePos,scrollLeft:0},500,"easeInSine",
              function(){!$("#popup").size() && $("#popup").bind("hover");});
 42:        jumpList[3] = {top:movePos+"px",left:0};
 43:        $(this).blur();
 44:        return false;
 45:      });
 46:    });
 47:  }
 48:
 49:  $(function(){
 50:    var txt = "この頁内で移動する";
 51:    jQ.each(function(){
 52:      var that = this;
 53:      that.pos = $(that).offset();
 54:      $destination = $($(that).attr("href"));
 55:      that.destinationPos = $destination.offset();
 56:      jumpList = [that.pos, that.destinationPos, that.pos];
 57:      that.guidePos = setGuidePos(o.guidePos);
          // ダミーアニメ(サイズ計測が適切に行われないため、一度起動する。
          // 但し何も表示させないよう透明化する)
 58:      $("body").animatedPopup(" ",{popupCSS:{opacity:0},queue:false,duration:10});
          // guide窓を若干透明にして表示する。
 59:      $("body").animatedPopup(txt,{popupCSS:that.guidePos,duration:200,easing:"easeInOutQuint"});
 60:      $(":animated").queue('fx',[]); // 登録済みのアニメを削除する
 61:      $animPopup = $animPopup===undefined ? $("#animPopup") : $animPopup;
 62:      $animPopup.children().eq(0).mouseover(function(){
 63:        if ($(this).text()===txt){
              // guide の替わりにジャンプ先リストを表示する。
 64:          displayJumplist.call(that);
 65:        } else return;
 66:      });
 67:    });
 68:  });
 69:  return jQ;
 70:};
 71:
 72:$.fn.backNforth.opts = {
 73:  moveStr : "<div accesskey='T'>(T) 頁最上部へ移動</div><div accesskey='1'
        >(1) 最初の位置へ移動</div><div accesskey='2'>(2) 次の位置へ移動
        </div><div accesskey='3'>(3) 直前の位置へ移動</div><div accesskey='B'>(B) 頁最下部へ移動</div>",
      // 0:center of window,1:leftTop,2:rightTop,3:rightBottom
      // 4:leftBottom,5:topEdge,6:rightEdge,7:bottomEdge,8:leftEdge
 74:  guidePos : 5
 75:};
 76:})(jQuery);

// backNforth プラグイン起動用スクリプト
$(function(){
	$("a[href*='contents'],[id*='contents']").addClass("backNforth");
	$(".backNforth").click(function(){$(this).backNforth({guidePos:5})});
});
 

任意の数の clone 要素を、任意の位置に配置する jQuery プラグイン

ID 名または class 名を持つ要素の clone 要素の活用

Web ページ内に存在する要素には一般に ID 属性や class 属性を有する要素が沢山存在し、それぞれに固有の ID 値が与えられ、あるいは複数の種類の class 名が、複数の要素に対して与えられています。

さて、多くの需要があるとは思えませんが、ID や class 名を有する要素を複写して、あれこれの位置に複数表示させたい場合が、時々発生します。例えば、popup 要素を複数同時に表示させたい場合等です。

あるいはページスクロールが発生した際に、或る位置に或る要素を常時表示させたい場合に、当該要素は元のままにして、clone 要素を作って常時表示させることにも活用出来ます。このように頁内での「影武者活用」は他にもあり得ると思われます。

今回作成した jQuery プラグインは ID 属性または class 属性を有する要素の存在を前提として、それらの clone 要素を任意の数だけ作成し、それをまとめて任意の要素の前後、内部あるいは外部に配置するものです。

要素を頁内に挿入するための各種 jQuery インスタンスメソッド

HTML 要素を Javascript で操作することは DOM の操作として大変興味深いものですが、jquery が登場するまでは、スクリプトを使って要素の配置換えや挿入を行うには、大変な労力を費やす必要がありました。

しかし、jquery が登場したことにより、要素の配置換えや挿入は極めて容易な行為に変化しました。因みに要素を操作する各種 jQuery インスタンスメソッドこそ、その容易さ故に jquery の威力を知らしめる格好のツールと言えるかもしれません。

さて、頁内の任意の位置に挿入配置するか、あるいは既に頁内に存在する要素を、任意の要素の前/後/内/外に配置換えするための jQuery インスタンスメソッドは数多く存在しています。よく知られていて利用されるメソッド群だと思いますが、列挙すれば以下の通りです。

凡例:A は操作対象とする要素で S、P、B は A 要素の配置対象とする要素

  1. S.after(A) or A.insertAfter(S) : A 要素を兄弟要素 S の直後に配置
  2. S.before(A) or A.insertBefore(S) : A 要素を兄弟要素 S の直前に配置
  3. P.append(A) or A.appendTo(P) : A 要素を親要素 P の最後の子要素として配置
  4. P.prepend(A) or A.prependTo(P) : A 要素を親要素 P の最初の子要素として配置
  5. P.html(A) : A 要素を親要素 P の子要素として配置
  6. P.wrapInner(A) : P 要素に子要素 A を挿入して、それ迄存在した全ての子要素を A の子要素( = P の孫要素)に変更する
  7. A.wrap(P) : A 要素(複数ある場合にはその各々)を P 要素で包含する
  8. A.wrapAll(P) : A 要素の全てをまとめて P 要素で包含する
  9. B.replaceWith(A) or A.replaceAll(B) : B 要素を A 要素で置換する

▲ToTop

プラグインの動作結果を実例で示します

何はともあれ、ぐだぐだ説明する前にどんなプラグインが完成したのか示すべきでしょう。

行われる一連の作業の概要は次の通りです。

  1. 以下のボタンをクリックすると、
  2. 直ぐに clone 要素の配置方法を選択する select ボックスが表示されます
  3. ボックス内のメソッドリストから配置メソッドを選択すると、
  4. この頁内にあって display:'none' で隠蔽されている ID名 'popup' 要素を元とする clone が作成され、
  5. 選択された配置メソッドによってボタン内に書かれた要素を配置基準として clone 要素が配置されます。

それぞれのボタンをクリックすると、要素配置用メソッドの一覧が選択枝として表示されるので、適当なメソッドをクリックしてください。すると指定した要素の前後、内部または外部に、popup 要素の clone が配置されます。

頁内に存在する ID 名「popup」要素の clone を 5 つ作成し、entry_body 部を配置対象として配置する。
頁内に存在する ID 名「popup」要素の clone を 3 つ作成し、この下のボタンを配置対象として配置する

実例2

今度は頁内に表示されている要素の clone を作成・配置します。ボタンをクリックして表示されるメソッドリストから、適当なメソッドを選択してクリックすれば、clone 要素が各々のボタンの近傍の指定した位置に配置されます。

まずページトップにある時計から。なお、元の時計は絶対配置されていてドラグ可能ですが、clone 時計は相対配置しますのでドラグは出来ません。

次はナビゲーションバーの clone を作って配置します。時計の場合もそうでしたが、makeNputClone 起動に際して第三引数の「 content 」を指定しなければ、第一引数である clone 元要素の clone がコンテンツとしてそのまま配置されます。

プラグイン設計上考慮したこと(1):数ある要素配置メソッドを選択的に適用させるために

プラグイン作成に際してまず検討したことは、上記のように沢山ある要素配置メソッドを、プラグイン使用時にどのように指定させるか、と言う点でした。

まさか、一々引数にメソッドを書き込んで指定する方法では、とても供用出来ません。実際、直前のエントリイで述べた方法は、まさに引数でメソッドを指定する類のものでした。

これではいけない、汎用性が全くないと判断し、その改善を思い立った訳ですが、select ボックスの活用を結論づけるまでにさほど時間は掛りませんでした。

しかし、select ボックスを表示させる方法やその配置方法などを考え始めた時、それをこのプラグイン内でゼロからコーディングすることは、いかにも稚拙であると思えました。

そこで閃きました。汎用性を持たせるために時間を掛けて改訂した animatedPopup プラグインメソッドを利用すればよい!───

このプラグインを使って、select ボックスをポップアップさせれば、clone 要素を配置した直後に、その位置までページスクロールさせるコーディングを行った時に、animatedPopup メソッドで表示させたポップアップ要素は、スクロールイベントに対応しているので、select ボックスも追随してスクロールされ、window 内の同じ位置に表示され続ける!

こうして、clone を作成・配置するプラグインは、その中から別のプラグイン animatedPopup を呼び出すものとなりました。

▲ToTop

プラグイン設計上考慮したこと(2):配置メソッドの選択が連続的に行えるように

このプラグインは clone 要素の作成それ自体よりもその頁内配置に興味を持っています。ですから。あれこれの配置を容易に試みることが出来なければ意味がありません。

そこで、連続して異なるメソッドを選択出来、かつ作成した clone 要素はその都度削除され、新たに作成された clone 要素を異なるメソッドで配置することにしました。

こうして各種配置メソッドを連続的に試すことを可能としたため、結果として各種配置メソッドの効果と差異を、一目瞭然に閲覧することが可能となりました。

2 つのプラグインから使用できる変数について

今回のプラグインで最も苦労したのは、makeNputClone メソッド内から animatedPopup メソッドを起動するにあたり、前者と後者で共通して利用する変数をどう定義するか、ということでした。

前者から一旦後者が呼び出されてしまった後でば、一般に前者内で定義した変数は後者では使用できませんし、前者から後者の変数も利用できません。引数を使って渡す方法もありますが、既に後者の引数定義は確定しており、makeNputClone メソッドからの呼び出しのために後者の引数を追加するのは邪道です。またそれぞれの関数のプロパティに変数を登録する選択肢もありますが、これは変数の記述文字数が多くなるので、コード作成上面倒です。

そこでグローバル変数である $ 変数のプロパティ利用を考えざるを得ませんでした。この方法は 「 余り美しくない 」 のかもしれませんが、他の方法を思いつかず、やむを得ないと判断しました。$.o プロパティを作り、そこに両方のメソッドで共通して使用する変数を登録することにしました。

▲ToTop

makeNputClone プラグインコード

詳細な解説は面倒なので、大きなブロック単位で簡単に説明します。

1 ~ 23 行【エラー処理ブロック】

引数をチェックしてエラーを表示するためのコーディングです。引数なしでプラグインを起動した場合には、簡易 HELP 機能が表示され、引数の指定方法を説明する alert が表示されるようにしました。

25 ~ 27 行【初期値整理ブロック】

初期値は例によって、$.extend クラスメソッドを活用して、既定値と引数値を併合します。プラグインメソッドの呼び出し元となる jQuery インスタンスを $.o プロパティに $target 名で登録します。

29 ~ 71 行 【 clone 作成・配置メソッド定義ブロック 】
fireMethod 関数は animate メソッドによるアニメーションが終わった時点で呼び出される関数として定義しました。その名の通り、配置「メソッド」を起動する機能を持たせました。

プラグインメソッドの起動元は、clone 要素の配置基準となる要素です。一旦プラグインを起動すれば select ボックス内からどのような配置方法を選択した場合でも、配置される clone はずっと同じものです。ですから配置の都度作成する必要はありません。

また、別の要素から当該プラグインを起動した後に、再び以前起動元とした要素から起動した場合でも、clone の作成元として指定した要素が替わらない限り、clone を作り直す必要はありません。

そのため、data クラスメソッドを活用して効率的なインタープリトが可能となるようにしました。( #52~54 )

73 ~ 85 行 【 DOM Ready・selectリスト表示アニメーションブロック 】

最初の 2 行は animate メソッドの complete 引数に代入する関数です。第二引数で makeNputClone メソッドの全ての引数を fireMethod 関数に渡しています。引数を一括指定出来る function.apply メソッドの大きな利点を活用しました。

DOM Ready 関数の最初で改めて第一引数 selector のチェックを行っています。これはエラー処理ブロックでは引数をその文字列で評価しましたが、ここでは指定した ID 名や class 名の要素の存在をチェックしています。

指定した名称が、jquery が解釈する文字列としては正しくても、肝心の該当要素が存在しなければ意味がないからです。

82 ~ 83 行で animatedPopup プラグインを使って、select ボックスを表示するアニメーションを引き起こします。

▲ToTop

88 ~ 164 行は既定値オブジェクトです。3 つのプロパティ別に説明します。

88 ~ 101 行 【 select ボックスリスト作成用 】

select ボックスを作成するための HTML 文字列です。

select リストの文字がデフォルトでは小さすぎるので、fontサイズを指定しました。

適用する配置メソッドの作用が分かりやすいよう、リスト文字列では配置先、メソッド、配置される要素の区別を表示しました。

102 ~ 162 行 【 配置メソッドオブジェクト 】

適用する配置メソッドをこのオブジェクトのプロパティ名とプロパティ値として格納しています。select ボックスでメソッドが選択されると、change イベントが発生し、対応するイベントハンドラー内で、このオブジェクトの関数が呼び出されて clone 要素が所定の位置に配置されます。

この部分で最も苦労したのは html メソッドです。作った clone 要素を敢えて html メソッドの引数として挿入するわけですから、HTML 文字列に変換しなければなりません。当然 id 名や class 名も保持させなければなりません。またスタイル属性値も最小限のものは適用させるようにしないと、clone らしくありません。

これらのハードルはそれぞれにかなり高く、文字列化とスタイル属性値の複写コードには相当苦労しました。

163 行 【 第一引数 selector の格納庫 】
同一要素からの二度目以降 のmakeNputClone メソッド呼び出しの際に、第一引数 selector の値を従前値のそれと比べるために使用します。52 行で行う比較が終わる迄は、このプロパティには、以前に起動された時の selector 名(但し、一度目の起動の際には null )が記憶されます。比較後には今回の selector 値がこのプロパティに保存されます(55 行)。
(function($){
$.fn.makeNputClone = function(selector,n,content,cloneCSS){
/* selector で抽出される最初の要素の clone を n 個作り、それを select
 * ボックス内から選択した jQuery メソッドによって、起動元インスタンス
 * の内外または前後に配置するメソッド。当該プラグインから animatedPopup
 * メソッドを起動し、select ボックスの表示に利用する。
 * selectBox から順次異なる配置位置を選択し、clone の配置換えが可能。
 * 一旦 clone を作成後に n を増やしたい場合にも、重複作成はしないよう
 * コーディングしてあるので、単純に n を指定すればよい。
 * 引数なしで起動すると引数の指定方法を alert 表示するようした。
 * release:2010/11/21
 */
  1:  var errFunc=function(al){al!==undefined && alert(al);return},p;
  2:  if (!arguments.length){
  3:    var text ="■引数の指定方法■\n"
  4:    text+="jQuery(elem).makeNputClone(selector,n,content,cloneCSS)\n\n";
  5:    text+="selector : string of original ID name\n";
  6:    text+="n : number of clones\n";
  7:    text+="content : HTML strings of clone's inner content\n";
  8:    text+="cloneCSS : CSS Object of clone";
  9:    return errFunc(text);
 10:  }
 11:  if (typeof selector !== "string" || selector.indexOf("#")<0 && selector.indexOf(".")<0){
 12:    return errFunc("指定した id 名や class 名の要素は存在しません。\n
          存在する id 名や class 名を指定してください。");
 13:  }
 14:  if (n===undefined || n===null || typeof n!=="number")
 15:    return errFunc("第二引数が不適切です。\n\n第二引数には、作成する
        clone の数を正の整数で指定してください。\n0 を指定すると作成済み
        の clone を削除します。");
 16:  if (content && typeof content !=="string"){
 17:    if (typeof content ==="object" && cloneCSS===undefined){
 18:      cloneCSS = content;
 19:    } else return errFunc("第三引数が適切ではありません。\n\n
 20:      第三引数は clone に挿入する HTML テキスト文としてください。\n
          content を指定しなければ、元要素のコンテンツが clone 要素の
          コンテンツになります。");
 21:  }
 22:  if (cloneCSS && typeof cloneCSS !=="object")
 23:    return errFunc("第四引数が適切ではありません。\n\n
        第四引数は clone に適用する CSS オブジェクトとしてください。\n
        cloneCSS を指定しなければ、元要素のスタイル設定が clone 要素に
        そのまま複写されます。");
 24:
 25:  $.o = $.data(this[0],"opts") || $.data(this[0],"opts",
 26:    $.extend({}, $.fn.makeNputClone.opts, {content:content || null,
          cloneCSS:cloneCSS || null, pluginFunc:arguments.callee,
          pluginArgs:arguments}));
 27:  $.o.$target = this;
 28:
 29:  var fireMethod = function(selector,n,content,cloneCSS){
 30:    var createClone = function(){
 31:      var clonesArray=[], $tmp=$(selector+i), temp, i=1, k=1;
 32:      for (;$tmp.length;i++) $tmp.remove();
 33:      for (; k< n+1; k++){
 34:        var temp = $($(selector)[0]).clone(true)
                .addClass(selector.slice(1) +"clone"+" draggable")
 35:        if (selector.indexOf("#")===0)
 36:          temp.attr('id',selector.slice(1)+k);
 37:        if ($.o.content)
 38:          temp.html('This is No.'+ k +' clone. '+$.o.content);
 39:        if ($.o.cloneCSS)
 40:          temp.css($.o.cloneCSS);
 41:        var $c = temp.children(":[id]");
 42:        if ($c.size()){
 43:          $c.each(function(){
 44:            $(this).attr("id", $(this).attr(selector.slice(1)+k));
 45:          });
 46:        }
 47:        clonesArray[k-1]=temp[0];
 48:      }
 49:      return $(clonesArray);
 50:    }
 51:    
 52:    $.o.$clone = $.o.beforeSelector === selector &&
 53:      $.data($.o.$target[0],"createClone") || 
 54:      $.data($.o.$target[0],"createClone",createClone());
 55:    $.o.beforeSelector = selector;
 56:    $("#thissel").change(function(){
 57:      var index = this.selectedIndex,  
 58:        resMethodName = this.options[index].value;
 59:      for (p in $.o.methodList){
 60:        if (p !== resMethodName) continue;
 61:        else {
 62:          $.o.methodList[p](); // cloneを頁に挿入する
 63:          var $its1=$("[class*='clone']:first");
 64:          $its1.length && $(window).scrollTop($its1.offset().top);
 65:          $its1.length && $its1.offset().left > $.F.getWinSize &&
                $(window).scrollLeft($its1.offset().left);
 66:          break;
 67:        }
 68:      }
 69:      // return $.o.$clone;
 70:    });
 71:  }
 72: 
 73:  var completeFunc = function(){
 74:    return fireMethod.apply($.o.$target, $.o.pluginArgs);
 75:  }
 76:  $(function(){
 77:    if (selector.indexOf("#")===0 && !$(selector).length){
 78:      return errFunc("指定した ID 名の要素は存在しません。\n
          存在する ID 名を指定してください。\nなお、冒頭に#を付ける
          ことを忘れないでください。");
 79:    } else if (selector.indexOf(".")===0 && !$(selector).length){
 80:      return errFunc("指定した class 名の要素は存在しません。\n
          存在する class 名を指定してください。\nなお、冒頭に '.' を
          付けることを忘れないでください。");
 81:    }
 82:    $.o.$target.animatedPopup($.o.selBoxHTML,{
 83:      popupCSS:{left:"center",top:"center"},duration:400,
            easing:"easeOutBack",complete:completeFunc
 84:    });
 85:  });
 86:};
 87: 
 88:$.fn.makeNputClone.opts = {
 89:  selBoxHTML :'<form><select id="thissel" name="thissel" size=12 style="font-weight:bold;font-size:16px">'
 90:    + '<option value="" disabled="desabled" style="text-align:center">メソッドを選択</option>'
 91:    + '<option value="append" selected="selected">target.append(clone)</option>'
 92:    + '<option value="prepend">target.prepend(clone)</option>'
 93:    + '<option value="after">target.after(clone)</option>'
 94:    + '<option value="before">target.before(clone)</option>'
 95:    + '<option value="wrap">target.wrap(clone)</option>'
 96:    + '<option value="wrapAll">target.wrapAll(clone)</option>'
 97:    + '<option value="wrapInner">target.wrapInner(clone)</option>'
 98:    + '<option value="replaceWith">target.replaceWith(clone)</option>'
 99:    + '<option value="replaceAll">target.replaceAll(clone)</option>'
100:    + '<option value="html">target.html(clone)</option>'
101:    + '<option value="remove">clone.remove</option></select></form>',
102:  methodList : {
103:    append: function(){
104:      $.o.methodList.remove();
105:      $.o.$target.append($.o.$clone);
106:    },
107:    prepend: function(){
108:      $.o.methodList.remove();
109:      $.o.$target.prepend($.o.$clone);
110:    },
111:    after: function(){
112:      $.o.methodList.remove();
113:      $.o.$target.after($.o.$clone);
114:    },
115:    before: function(){
116:      $.o.methodList.remove();
117:      $.o.$target.before($.o.$clone);
118:    },
119:    wrap: function(){
120:      $.o.methodList.remove();
121:      $.o.$target.wrap($.o.$clone);
122:    },
123:    wrapAll: function(){
124:      $.o.methodList.remove();
125:      $.o.$target.wrapAll($.o.$clone);
126:    },
127:    wrapInner: function(){
128:      $.o.methodList.remove();
129:      $.o.$target.wrapInner($.o.$clone);
130:    },
131:    replaceWith: function(){
132:      $.o.methodList.remove();
133:      $.o.$target.replaceWith($.o.$clone);
134:    },
135:    replaceAll: function(){
136:      $.o.methodList.remove();
137:      $.o.$clone.replaceAll($.o.$target);
138:    },
139:    html: function(){
140:      $.o.methodList.remove();
141:      var text="";
142:      $.o.$clone.each(function(){
143:        text +="<"+this.nodeName+" id='"+ +$(this).attr("id")+
            "' class='"+$(this).attr("class")+"'>"+$(this).text()+
            "</"+this.nodeName+">";
144:      });
145:      $.o.$target.html(text);
146:      var clone = $("[class*=clone]");
147:      for (var i=0; i<clone.length; i++){
148:        $.each(["width","height","margin","border","padding",
              "background"],  function(){
149:          clone[i].style[this] = $.o.$clone[i].style[this];
150:        })
151:      }
152:    },
153:    remove: function(){
154:      var clone = $("[class*=clone]");
155:      if (clone.length) {
156:        if (clone.has($.o.$target).length){
157:          $(clone[0]).before($(clone[0]).children());
158:        }
159:        clone.remove();
160:      }
161:    }
162:  },
163:  beforeSelector:null
164:}
165:})(jQuery);

▲ToTop

コード解説を行うためにコード全文から抜粋を行うコード

上記の説明では副題をクリックすると該当部分のコードが抜粋表示されます。その表示にも animatedPopup メソッドを活用したのは余興ですが、初めて採用したこの方法では、一箇所に説明用コードを全文書き込むだけで、後は抜粋作業を Javascript を使って行わせれば、容易に抜粋が可能となることを実証したつもりです。

長いエントリイの最後に、コード全文からその一部を抜粋するためのコーディングについて触れておきます。

ネームエンティティ問題

最初は、ブラウザに表示されたエントリイ内のコード全文を検索対象として、抜粋を行おうとしました。ところがこの方法には大きな落とし穴が待っていました。ネームエントリイ問題です。

コード全文掲載に当たっては、HTML 文上意味のある記号(全角で表記すると、"<"、">"、"&" 等)をネームエンティティ変換しててから、エントリイを投稿しています。それによってブログが描画される際に、これらの特殊記号は HTML 文上意味のないものとして扱われ、正しく表記されることになります。こうして、例えば投稿時には &lt; と半角で書いていた記号は、< になって表記されることになります。

さて、この正しく表記されたエントリイ上の文字列から、Javascript を使って抜粋対象を抽出すると、その抽出部分に < が含まれていた場合に、そのまま抽出結果をブログに貼り付けると問題が発生します。単なる記号としての < ではなく、タグ開始文字として HTML 文上意味のある記号となってしまうのです。

この結果、ブログエントリイから部分抽出した結果をそのまま投稿すると、表示が崩れてしまうのです。

再びネームエンティティ変換して原稿とすればよい、と考え試みてみましたが成功しませんでした。投稿した時点のネームエンティティ変換と全く同一にはならないのです。空白文字(改行マークや半角スペースなど)で区切られていない ; がネームエンティティ表記の;と誤解されるらしく、正しく復号することが出来ないのです。(ネームエンティティ変換実施(A)→ブラウザで表記(B)→再度のネームエンティティ変換(C)と辿った時に、A と C は同一にはならないのです)

解明しませんでしたが、他にも正しい「復号」を妨げる要因が複数存在しているようでした。

そこで AJax 通信を使用した

試行錯誤の結果、表記されたブラウザ内の全文コードから Javascript を使って部分抜粋を行い、抜粋部分を再び投稿する方法は、適切ではないと判断するに至りました。

そして替わりに思いついたのが、Ajax 通信を活用することでした。これによりブログエントリイ全文をテキスト形式で取得し、その中から必要部分を抜粋すればよい。そうすればネームエンティティ変換した文字は原文のまま、変換されたままで取得できます。つまり、そこから抜粋した文字列もネームエンティティ変換されたままの状態なので、意図したとおりの結果に辿り着ける───こう考えたのです。

そしてそれは見事に成功しました。

そのコーディングは以下の通りです。

  1:  var pageBody;
  2:  $.ajax({
  3:    url: このサイトのurl,
  4:    dataType:"text",
  5:    success: function(data){
  6:      pageBody = data;
  7:    }
  8:  });
・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  9:  var selectText = function(start,end){ 
 10:    return this.slice(this.indexOf(start),this.indexOf(end)-1);
 11:  };

利用は 1 行目から 8 行目がブログ・エントリイ・オープン時に実行させることを前提にしています。開いた暫く後になってからしか、コードの部分閲覧は発生しないので、その頃には十分 Ajax 通信は complete している訳で、pageBody 変数に通信結果が代入されているはずです。

つまり、Ajax 通信の成功有無はチェックする必要がないと判断しました。

利用は、selectText.call(pageBody,"抽出開始文字","次の抽出開始文字") で行います。敢えて、pageBody から関数を call 起動し、呼び出された関数内で this が pageBody を参照するようにしました。

「開始文字」から「次の開始文字」の 1 文字前迄の文字列群を、Ajax 通信で取得したブログページ全文文字列情報 pageBody から sliceする訳です。これによりコード全文から、必要部分を抜粋することが出来ます。

具体的に抽出例に触れてみます。

下のコードはコード全文から、「 1:」で始まる部分から、「 24:」で始まる部分の 1 文字前迄を抽出し、それを pre タグで囲んで animatedPopup メソッドの第一引数とし、その他当該メソッドに必要な引数を指定して、animatedPopup メソッドを起動するコーディングです。

ID 名 「dt792-1」要素のクリックイベントハンドラーとして、animatedPopup 関数を登録しています。

padding 指定を行っていますが、ブログテンプレートの指定が優先されてしまうようで、引数で指定した padding 通りには animatedPopup しません。これは animatedPopup において popup アップするコンテンツは div 要素で囲まれているのですが、この div 要素にテンプレート固有のスタイル設定が施されているためであると、推測できます。

$("#dt792-1").click(function(){
  $(this).animatedPopup("<pre id='code792-1' class='point ml_1 lh_11 ov_a'>"
    + selectText.call(pageBody,"  1:"," 24:")+ "</pre>",
    {popupCSS:{top:"10px",right:"10px",width:600,origin:2,textAlign:"left",
    paddingLeft:"3px !important",paddingTop:"3px !important",paddingBottom:"3px !important",paddingLeft:"3px !important"}});
}); 

自作プラグイン-アニメーションポップアップを全面改訂

恐縮ですが animatedPopup プラグインは IE では動きません。原因は jquery.js ver 1.4.2 の CSS スタイルに係る部分に、IE に対してバグとなってしまう箇所があるようです。この問題は ver 1.4.4 でも解決していないようです。

animatedPopup プラグインを全面的に改訂した

1 年半ほど前に作成した animatedPopup プラグインは、いわば「勢い」で作ったものでプラグインとしての基本的作法は全く考慮せず、我流の作品に止まっていました。

しかし、真にプラグインとして汎用性を持たせるには、スクリプトコードの論理性・明快性・可読性等を考慮しなければならないと考えるに至り、この度数ヶ月を掛けて全面改訂を行いました。

なお、今回の改訂は各種プラグインから利用出来る、完成済みの jQueryクラスレベルの関数オブジェクト jQuery.F を前提に開発しています。その $.F については、こちら( 複数の jQuery プラグインで共有するオブジェクトを無名関数内に定義する )で詳細に説明してあります。

▲ToTop

animated Popup メソッドの実例

ポップアップ表示をアニメーションメソッドを使って行うことを意味しています。例えば極小の点から一定の幅と高さを有する表示ボックスをズームポップアップし、それを消すときにはズームアウトする、そんなポップアップです。

何はさておき、その実例を示すことが先決でしょう。

下のボタンをクリックするとあるいはその近傍に、あるいは画面中央に、あるいは画面右下に、あるいは指定してある位置 {left:100px,top:200px} に、ポップアップがズームイン/アウトされます。表示する際のポップアップサイズは順に300、350、400、500px に、またアニメーションに要する時間はそれぞれ 1200、600、1600、2000、2500 ミリ秒に設定してあります。

 クリックした位置の傍に、左上から現れ、左上に消えるポップアップを表示します。

クリックすると画面の中央に、中心から現れ中心に消えるポップアップを表示します。

クリックすると画面の右下に、右下から現れ右下に消えるポップアップを表示します。

クリックすると指定してある位置に、左側から現れ左側に消えるポップアップを表示します。

クリックすると画面の中央に、上部からぶら下がり上部に巻き上がるポップアップ内に或る写真を表示します。写真出典元は フォトライブラリ です。

▲ToTop

animatedPopup に様々な Easing を適用する

George Smith 氏により提供されている jQuery プラグイン「Easing Plugin」は 10 種類あり、各々に 3 つの効果(easeIn、easeOut、easeInOut)を持たせて Easing 関数が定義されているため、都合 30 種類の easing 関数があります。→ Plugins | jQuery Plugins

その Easing の各々の動きの違いを示したサイトとして興味深いのは、flash ムービーですが easing_demo が実に分かりやすく有益です。これによりそれぞれの Easing がどんな挙動をするのか一目瞭然に理解できます。easing 関数内の代数式がグラフ化されていることも非常に有益です。

そこで、このデモサイトにあやかって、jquery.js 内蔵の linear と swing イージングを加えた 32 種類の Easing 効果を、animatedPopup メソッドに適用するリストボックスを設置してみました。easing_demo に比べて文字表示の場合には各 easing の差異がわかりにくいのですが、まあそれはご愛敬ということで...。

下のリストボックスの説明
  • アニーメーションポップアップの表示位置は 「 画面中央 」 としました。
  • Easing に要する時間はそれぞれの関数の差異が分かり易いように長めに設定しました(2秒)。
  • 別の easing 関数を選択する前に表示されているポップアップを隠蔽する必要はありません。自動的に前の表示を消して、新たな表示を行います。

▲ToTop

全面改訂に当たって心掛けたこと

アンダーライン箇所には、マウスオーバーポップアップによる補足説明が設定されています。

  1. プラグイン内で作成する関数名を、その役割が一目で分かるようなものとしました。命名は基本的に「動詞+関数の機能や内容をを表す名詞」としました。
  2. 作成した関数は、それぞれが果たす役割が明確になるよう基本的に単機能としました。こうしておくとメンテナンスがしやすいからです。
  3. 各種初期値用のオブジェクトを $.fn.animatedPopup.opts プロパティに用意しました。この方法は初期値の明確化、可読性向上、メンテナンスの容易さ等のメリットがあります。
  4. 初期値に対して、可能な限りアニメーションポップアップ要素のCSS値(色、サイズ、位置等々)を自在に変更できるようにコードを工夫しました。
  5. 引数をどのように設定したのか、時間が経つほどにとかく忘れてしまうものです。そこで 特別な文字「?」(半角全角は問わない)を引数にすれば、window.alert 関数で引数の指定方法を表示するようにしました。これで引数の指定内容がいつでも分かるようになりました(いわば簡易 HELP機能です)。
  6. コード内での関数呼び出しには function.call メソッドを多用しました。呼び出された関数の実行時に、キーワード this が当該関数の呼び出し元オブジェクトを参照することを利用して、特定のオブジェクトを当該関数内で参照しやすくするためです。
  7. 当然のことですが、メソッドチェーンを可能とするために animatedPopup 関数の返値は jQuery インスタンスとしました。
  8. 初めて行ったことですが、プラグイン関数のプロパティにプラグイン関数の内部関数を登録し、プラグイン関数が終了した後でもその内部関数を呼び出せるようにしました。

▲ToTop

プラグインコード解説

コードは 200 行強の比較的短いものです。ポップアップ要素のスタイル設定もスタイルシートは使わずスクリプトで行いました。

以下コード全体をいくつかのブロックに分けて解説します。なお、末尾にはコード全体を掲載してあります。

変数設定

トップレベルに定義した変数は animatedPopup 関数全体で使用するものに限定しました。$ で始まる変数は jQuery インスタンスへの参照のためであることを示しています。

errFunc は文字通りエラー発生時に使用する関数です。errFlag 変数に true を代入することにより、animatedPopup 関数の動作を停止します。

  2:$.fn.animatedPopup = function(content,opts){
  3:  var jQ = this,errFlag = false,wait,ival,$animPopup,$xMark,
  4:      errFunc = function(){errFlag=true; return false;};
エラー対応および HELP コンテンツ定義及び各種の値の設定 makeCSS

makeCSS 関数は、$.fn.animatedPopup.opt で定めた初期値と引数 (content,opts) から、プラグイン起動元である jQuery インスタンスに対して適用する様々な値を作成するものです。作成値は CSS 値だけではありませんが、CSS 値作成部分が大半なので代表させて makeCSS と命名しました。

  5:  var makeCSS = function(){
  6:    this.startScrollPos = $.F.getScroll(window);
  7:    this.startWinSize = $.F.getSize(window);
  8:    if (opts && opts.constructor!== Object ){
  9:      alert("配置はオブジェクト形式で指定してください。\n
            例1 popupCSS:{left:\"10px\",top:\"100px\"}\n
            例2 popupCSS:{right:\"20px\",bottom:\"100px\"}");
 10:      errFunc();
 11:    };
 12:    if (!content || content==="") {
 13:      alert("ポップアップする内容が指定されていません。");
 14:      errFunc();
 15:    }
 16:    if (content==="?" || content==="?"){
 17:      var text ="jQuery(anyElement).animatedPopup(contents,opts);\n";
 18:      text +="contents: HTML strings\n";
 19:      text +="opts: Object \n";
 20:      text +="{ popupCSS:{CSS styles set of animatedPopup Element},\n";
 21:      text +="  closeBarCSS:{CSS styles set of closeBar Element},\n";
 22:      text +="  duration:time, easing:name, queue:true or false,complete:function\n";
 23:      text +="}";
 24:      alert(text);
 25:      errFunc();
 26:    }
        // popup 用の各種 CSS オブジェクトを作る。
 27:    var ret= $.extend({}, $.fn.animatedPopup.opts, {content:content}, opts &&  opts.constructor === Object && opts || {});
       // ret のサブオブジェクトが引数 opts により上書きされて
        // しまっている場合があるので、改めて拡張する
 28:    ret.popupCSS = $.extend({}, $.fn.animatedPopup.opts.popupCSS, ret.popupCSS);
 29:    ret.popupOffset = $.extend({}, $.fn.animatedPopup.opts.popupOffset, ret.popupOffset);
 30:    ret.closeBarCSS = $.extend({}, $.fn.animatedPopup.opts.closeBarCSS, ret.closeBarCSS);
 31:    ret.initPopupCSS = $.extend({},ret.popupCSS);
 32:    return ret;
 33:  }
  1. 6~7 行の this.start で始まる 2 つの変数は、ポップアップ要素表示後に window をスクロールしたり、window のサイズを変更した時に、それらのイベントに対応してポップアップ要素の位置を変化させるために使用するもので、animatedPopup 関数起動時の window スクロール値とサイズを取得します(詳細は後述)。
  2. 8~26 行は引数エラーチェックと HELP コンテンツの作成です。引数チェックは、当然 jQuery インスタンス毎に行うことになりますが、インスタンスが複数の要素からなる場合には引数が要素毎に異なる場合もありえます。そこで makeCSS 関数内にエラーチェックコードを配置し、インスタンスの要素毎にチェックするようにしました。
  3. エラーチェック後に、初期値オブジェクトに引数を合成したオブジェクトを作ります(27 行)。このとき、extend クラスメソッドが極めて効果的に働くことは言うまでもありません。このメソッドは、先置した引数オブジェクトのプロパティが後置したオブジェクトにも存在していれば、後置オブジェクトのプロパティ値で上書きし、後置オブジェクトのプロパティが前置オブジェクトに存在しなければ前置オブジェクトに当該プロパティを追加し、複数オブジュエクトを合体させます。この結果、前置オブジェクトのプロパティ値が後置オブジェクトの同一名プロパティ値で置き換えられ、前置オブジェクトになかった後置オブジェクトのプロパティは前置オブジェクトにそのまま追加されます。
  4. こうして初期値を引数値で上書きしますが、初期値のプロパティ値がオブジェクトの場合には、当該オブジェクトが opts オブジェクトの同一プロパティ名のオブジェクトで丸ごと上書きされるため、補正が必要となります。その補正を 28~30 行で行います。
  5. 補正もまた extend メソッドを使います。値がオブジェクトである初期値プロパティに、27 行で作成された同一名のプロパティ値を併合させることにより、27 行目で一部消失してしまった初期値オブジェクトの全てのプロパティを復活させ、これに、27 行目で作成した同一プロパティ名の値を上書きします。こうして全ての初期プロパティを保持したままで、その値を引数値で上書きしたオブジェクト ret が完成します。
  6. 31 行の initPopupCSS プロパティは popupCSS プロパティの初期値を保持するためのもので、window リサイズイベントハンドラー関数内で使用します。

▲ToTop

ポップアップ要素のサイズ計測関数 getSize

ポップアップされるコンテンツ毎にポップアップ要素の幅も高さも変化します。そしてその都度変化するポップアップ要素のサイズに応じて、ポップアップ要素の位置を適切に算出しなければなりません。

しかし、要素サイズは一般にそれが描画されなければ確定できません。文字列の場合特にそうです。最も簡単な解決方法は、全てのコンテンツに対して同一の幅とし、高さは自動的にブラウザに決めさせれば良いのですが、これでは余りに芸がありません。出来る限りコンテンツに相応しい幅と高さにしたいものです。

従って要素を適切にポップアップするには、描画する前にどのようにしてその幅と高さをブラウザに認識させるのか───これがポイントとなります。

 35:  var getCloseBarHeight = function(){
 36:    $animPopup.css({display:"block",visibility:"hidden"});
 37:    var temp = $xMark.outerHeight(true);
 38:    $animPopup.css({display:"none",visibility:"visible"});
 39:    return temp;
 40:  }
 41:  var getSize = function(){
 42:    var ret = this.o, pCSS = $.extend({},ret.popupCSS);
        // popup 要素にコンテンツを挿入し、popup 要素の width と height を計測
        // 1.right や bottom が指定された場合高さを正確に測れなくなるので、
        // これらの属性を削除する。
 43:    if (pCSS.right !== undefined) delete pCSS.right;
 44:    if (pCSS.bottom !== undefined) delete pCSS.bottom;
        // 2.既定の幅と高さをキャンセル後に $animPopup's width を計測し、
        // コンテンツを挿入する。
 45:    $animPopup.css({width:null,height:null,top:0,left:0})
 46:      .children().eq(0).css({width:null,height:null}).html(ret.content);
 47:    var cW = $animPopup.width();
        // 3.幅値を最適化する
 48:    cW = ret.content.match(/\ssrc/) ? cW : Math.min(cW,parseInt(pCSS.width));
        // 4.padding 値を調整する
 49:    pCSS.paddingTop = getCloseBarHeight() + parseInt(pCSS.paddingTop) + "px";
        // 5.得られた幅値を適用して隠蔽状態のまま
        // $animPopup.css メソッドを実行し、各種サイズを計測・取得する
 50:    ret.popupSize = $.F.getSize($animPopup.css($.extend(pCSS,{width:cW+"px"}))[0]);
        // 6.コンテンツが画像などではなく、かつ、幅か高さが 600 px 以上
        // の場合にはスクロールバーを配置する
 51:    if (!ret.content.match(/\ssrc/) && (ret.popupSize.cW >=600 || ret.popupSize.cH >=600)) ret.popupCSS.overflow="auto";
        // 7.取得した padding 値をポップアップ要素表示 CSS に設定する
 52:    ret.popupCSS.paddingTop = pCSS.paddingTop;
        // 8.取得したコンテンツサイズをポップアップ要素表示 CSS に設定する
 53:    ret.popupCSS.width = $animPopup.children().eq(0)[0].style.width = ret.popupSize.cW +"px";
 54:    ret.popupCSS.height = $animPopup.children().eq(0)[0].style.height = ret.popupSize.cH +"px";
 55:  }
サイズ計測のための付随関数 getCloseBarHeight(35~40 行)
  1. この関数はポップアップされた要素を閉じるための部品である closeBar の高さを計測するもので、計測結果を使ってポップアップ要素の paddingTop 値を補正します。わざわざこのようにしたのは、closeBar の CSS 値も引数で変更出来るため、padding 値を自在に設定出来るようにしておかないと使い勝手が悪くなるからです。
  2. この関数の肝は、display:none 状態の要素内にある、子要素のサイズ計測をどのように行うか、にあります。親要素の display 属性が none である限り、子要素に対して width メソッドを適用してもサイズは測れません。ここでは jQuery.swap クラスメソッドで使用している方法を利用しました。
  3. CSS 値を display:block、visibility:hidden に変更してからサイズを計測します。これにより描画させつつ表示させない状態を作って計測可能とします。計測後に display:none、visibility:visible に戻す必要があることは言うまでもありません。
サイズ計測のための前処理(42~44 行)
  1. HTML 要素の描画前にその大きさを知る為に必要となるのは、ポップアップすべきコンテンツそのものと、ポップアップ要素の CSS 値です。(なお、CSS 値は必要不可欠ではありません。また、画像や動画の場合には、要素の属性値に幅などが記載されている場合が多いため事情が異なりますが、そのことはさしあたり無視します。)
  2. しかし、いきなり CSS 値をポップアップ要素に適用すると誤った計算をしてしまう場合があります。何故ならば、CSS値には right や bottom プロパティが存在する場合があり、これらのプロパティがあるとポップアップ要素サイズは、最大で top:0、left:0、right:0、bottom:0 で囲まれた範囲、すなわちブラウザの window サイズになってしまうからです。
  3. そこで一工夫が必要となります。どんな場合であっても right 及び bottom プロパティを強制的に削除した作業用 CSS オブジェクトを用意すれば良いと考えました。その作業用 CSS オブジェクトとコンテンツから、ポップアップ要素サイズを計測すれば、当該要素ボックスの右下隅が window 右下隅になってしまう事態を避けることが出来ます。
  4. 42 行で宣言した変数 pCSS は、この作業用 CSS オブジェクトを参照させるものです。
  5. これに先立って、変数 ret に this.o を代入していますが、この this は jQuery インスタンスに登録されている要素を参照しています。後述するように jQuery インスタンスに登録されている要素を起動元として、getSize 関数に対して call メソッドを適用するからです。
ポップアップ要素の横幅計測
  1. ブラウザに対象要素を表示させることなく当該要素の幅や高さを知るには、jquery.js の inner メソッドである swap クラスメソッドを使う方法があります。しかしここでは、swap メソッドは使用せず、それが使用しているロジックを踏襲してサイズ計測を行いました。
  2. jquery.js は、隠蔽した要素に width メソッドを適用すると、算出スタイル値を取得するよう設計されているので、これを利用すれば良いわけです。但し計測対象の親要素が隠蔽状態にないことが前提となります。(別のエントリイでその具体的な利用方法を記述しました。)
  3. このコードでは 45~47行において、ポップアップ要素へのコンテンツの挿入と横幅計測を行っています。

▲ToTop

ポップアップ要素の横幅設定
  1. ポップアップする要素の大きさは、一般的に何通りかの設定スタイルがあります。ポップアップされる要素が何であれ、全て同一の幅に設定することもあれば、何通りかの既定値を定めておいて、コンテンツに応じて選択的に既定値を選択する方法もあるでしょう。あるいはコンテンツの種類(画像か文字列化など)に応じて何通りかの幅を設定する選択肢もあるでしょう。
  2. このコードでは 48 行でコンテンツ幅の設定を行いますが、画像の場合にはそのコンテンツが持つ固有の幅をポップアップ要素のコンテンツ幅とし、それ以外の場合には既定のコンテンツ幅と取得したコンテンツ幅とを比べて小さい方を採用することとしました。
  3. 言い換えれば、文字列の場合のコンテンツ幅は、固有幅が既定値に満たない場合にはその固有幅に、固有幅が既定値を超える場合には既定値幅に設定します。なお、既定値は初期値として定めた値を、引数で変更することが出来るようにしました。つまり、固有値、初期値及び引数で定めた値の 3 つから選択的に幅が決定されます。(なお、動画表示も十分可能ですが、さしあたり今後の課題とします。)
ポップアップ要素の paddingTop 値計測

getCloseBarHeight 関数を使って closeBar の外寸高さを取得し、これに既定の paddingTop 値を加算して、新たな paddingTop 値を取得します。後は取得した値と既定値を組み合わせて新しい padding 値を作るだけです。

ポップアップ要素の各種サイズ計測
  1. 横幅の計測と設定を終えれば、後はその他のサイズ(コンテンツ高さ、マージン辺幅など)を取得するだけです。その計測は 50 行で行っていますが、ここでは自作のプラグイン共用関数を使用しました。$.F.getSize(elem) 関数を使えば、elem 要素のコンテンツ、パディング辺間、ボーダー辺間及びマージン辺間の、各々の幅(cW、pW、bW、mW)と高さ(cH、pH、bH、mW)を取得することが出来ます。
  2. ここに、幅と高さをコンテンツだけでなく各種辺間距離も取得するのは、window からのポップアップ要素のはみ出し防止のために、要素マージン辺間の幅と高さを利用するためです。確かに、パディング辺間距離とボーダー辺間距離はこのコード内では利用しませんが、自作のサイズ計測関数があるので序でに取得したまでです。
  3. 51 行ではポップアップ要素サイズが大きな場合に必要となるスクロールバーの配置を行います。幅又は高さが 600 px を越えた場合には要素にスクロールバーを配置するよう、overflow 属性を "auto" に指定します。
  4. 以上による各取得値を、52~54 行で当該要素の各種プロパティに代入しアニメーションに備えます。

▲ToTop

ポップアップ要素の配置位置決定 setPos

以上でポップアップ要素の大きさが確定したので、次に行うべきことは引数などに応じて当該要素の配置位置を確定することです。つまり、top 値と left 値を与条件から算出し確定することです。そのための関数が 56~77 行の setPos 関数です。

 56:  var setPos = function(){
 57:    var pCSS = this.o.popupCSS, offCSS = this.o.popupOffset, pSize = this.o.popupSize;
        // right や bottom 指定がされた場合
 58:    if (pCSS.right !== undefined) {
 59:      pCSS.left = this.startWinSize.cW - pSize.mW - parseInt(pCSS.right) +"px";
 60:      delete pCSS.right;
 61:    }
 62:    if (pCSS.bottom !== undefined){
 63:      pCSS.top = this.startWinSize.cH - pSize.mH - parseInt(pCSS.bottom) +"px";
 64:      delete pCSS.bottom;
 65:    }
        // left 値や top 値が "center" で指定された場合
 66:    if (pCSS.left ==="center") pCSS.left = this.startWinSize.cW/2 - pSize.mW/2+"px";
 67:    if (pCSS.top ==="center") pCSS.top = this.startWinSize.cH/2 - pSize.mH/2+"px";
        // mouse cursor 近傍に popup する場合の位置指定
        // left 値か top 値に false を指定することによりその旨を指定する
 68:    if (pCSS.left === false || pCSS.top === false) {
 69:      pCSS.left = parseInt($.F.mousePos.X) + parseInt(offCSS.left) + "px";
 70:      pCSS.top = parseInt($.F.mousePos.Y) + parseInt(offCSS.top) +"px";
 71:    } else {
        // mouse cursor 近傍以外の場所に表示する場合には scroll 値を加算
 72:      pCSS.left=parseInt(pCSS.left) + this.startScrollPos.L+"px";
 73:      pCSS.top=parseInt(pCSS.top) + this.startScrollPos.T+"px";
 74:    }
        // 画面外への飛び出し防止補正
 75:    pCSS.left = Math.max(0, Math.min(this.startWinSize.cW + this.startScrollPos.L - pSize.mW, parseInt(pCSS.left)))+"px";
 76:    pCSS.top = Math.max(0, Math.min(this.startWinSize.cH + this.startScrollPos.T - pSize.mH, parseInt(pCSS.top)))+"px";
 77:  }

位置決めは引数で与えられた情報によりケース分けして行いますが、全てのケースにおいて、自作のプラグイン共用関数をフル活用しています。

  1. 引数で位置指定を行わない場合には、top も left も初期値を "center" と定めたので、ポップアップ要素は window 中心に表示されることになります。
  2. 引数で right や bottom 属性が指定された場合には、問題を単純化するためにも left やtop に置換し、right や bottom 属性を削除します。(58~65行)
  3. 引数で top や left 値に center が指定された場合の対応を 66、67行で行っています。
  4. 引数で top 又は left 値に false を指定すると、それはマウス位置の近傍にポップアップ要素を表示することを意味するように定めました。false が指定された場合の対応は、68~71 行で行い、top も left も false 値が指定されていない場合には、top 値 と left 値に window のスクロール値を加算します。ここでも自作のプラグイン共用関数をフル活用しています。(72、73行)
  5. 最後に、画面外へのポップアップ要素の飛び出しを防止する措置を 75、76 行で講じています。
ポップアップ要素の展開/縮小を演出する setShrinkOuterCSS

アニメーションポップアップの要点は当該要素の「登場・退場」の仕方にあります。

アニメーションとは言い換えれば、要素の CSS 現在値が CSS 到達値に変化する過程が動的に表示されることに他なりません。従って、隠蔽状態から表示状態へ、そしてその逆への 2 つの過程を演出するには、隠蔽状態の CSS 値を作成しなければなりません。その隠蔽状態 CSS 値を setShrinkOuterCSS 関数で作成します。

なお、OuterCSS と命名したのはポップアップ要素のマージン辺内を対象としてシュリンクさせるからです。

 79:  var setShrinkOuterCSS = function(){
 80:    var pCSS = this.o.popupCSS,
 81:        pSize = this.o.popupSize,
 82:        Xdir = pCSS.origin ===5 || pCSS.origin ===7,
 83:        Ydir = pCSS.origin ===6 || pCSS.origin ===8;
 84:    return {opacity:0,
 85:      borderLeftWidth: Xdir ? pCSS.borderLeftWidth : 0,
 86:      borderRightWidth: Xdir ? pCSS.borderRightWidth : 0,
 87:      borderTopWidth: Ydir ? pCSS.borderTopWidth : 0,
 88:      borderBottomWidth:  Ydir ? pCSS.borderBottomWidth : 0,
 89:      paddingLeft: Xdir ? pCSS.paddingLeft : 0,
 90:      paddingRight: Xdir ? pCSS.paddingRight : 0,
 91:      paddingTop: Ydir ? pCSS.paddingLeft : 0,
 92:      paddingBottom: Ydir ? pCSS.paddingBottom : 0,
 93:      marginLeft: Xdir ? pCSS.marginLeft : 0,
 94:      marginRight: Xdir ? pCSS.marginRight : 0,
 95:      marginTop: Ydir ? pCSS.marginTop : 0,
 96:      marginBottom: Ydir ? pCSS.marginBottom : 0,
 97:      width: (Xdir ? pSize.cW : 0) +"px",
 98:      height: (Ydir ? pSize.cH : 0) +"px",
 99:      width: ((pCSS.origin ===5 || pCSS.origin ===7) ? pSize.cW : 0) +"px",
100:      height: ((pCSS.origin ===6 || pCSS.origin ===8) ? pSize.cH : 0) +"px",
101:      left:parseInt(pCSS.left) + 
102:        (pCSS.origin ===0 ? pSize.mW/2 :
103:        pCSS.origin ===2 ? pSize.mW :
104:        pCSS.origin ===3 ? pSize.mW :
105:        pCSS.origin ===6 ? pSize.mW : 0) +"px",
106:      top:parseInt(pCSS.top) +
107:        (pCSS.origin ===0 ? pSize.mH/2 :
108:        pCSS.origin ===3 ? pSize.mH :
109:        pCSS.origin ===4 ? pSize.mH :
110:        pCSS.origin ===7 ? pSize.mH : 0)+"px"
111:    }
112:  }
  1. この関数の肝は origin プロパティによる CSS 属性値の編集設定です。
  2. origin プロパティは、初期値 0 を $.fn.animatedPopup.opts.popupCSS.origin に定めておき、makeCSS 関数により要素の o.popupCSS.origin に複写させます。
  3. origin 値は 0:center of element,1:leftTop,2:rightTop,3:rightBottom,
    4:leftBottom,5:topEdge,6:rightEdge,7:bottomEdge,8:leftEdge の 9 つの値を取ることが出来るよう設計しました。(これらの値以外が与えられるとエラー関数処理を行いコード進行を停止します。)
  4. 0~8 の値は順に、表示状態の対象要素の中心、左上隅、右上隅、右下隅、左下隅、上辺、右辺、下辺、左辺の位置を意味します。origin 値の初期値は 0 、つまり要素の中心から登場し、中心に消える設定にしてありますが、引数で任意に指定することにより、9 つの起終位置を指定することが出来ます。
  5. setShrinkOuterCSS 関数において、アニメがある一点から始まり、その点に収斂する場合には(これを便宜的に「点モードアニメ」と呼びます)、隠蔽状態の各種 CSS 値をゼロとしていること、これに対して線モードアニメの場合には、アニメ終了線が要素の上下の時には幅値を固定し、アニメ終了線が要素の左右の時には高さ値を固定していることに注目してください。

▲ToTop

popup 要素タグの作成

以上でポップアップ要素のサイズと位置を決める関数が終わりました。続いてポップアップ要素タグそのものを Web 頁に挿入するコードです。

113:  var makePopupElem = function(){
114:    if (!$("#animPopup").size()) {
115:      $("<div id='animPopup'></div>").css({position:"absolute",display:"none",zIndex:"1000"})
            .append("<div></div>")
            .appendTo(document.body);
116:    }
117:    $animPopup=$("#animPopup");
        // Popup 隠蔽用×タグの作成
118:    if (!$("#xMark").size()){
119:      $("<div id='xMark'>CLOSE</div>").css($.fn.animatedPopup.opts.closeBarCSS)
            .append("<div style='position:absolute;z-index:1003;top:0;right:2px;width:13px'>×</div>")
            .appendTo($animPopup);
120:    }
121:    $xMark = $("#xMark");
122:  };
  1. この部分は他言は要しないと思いますので、最小限の説明に留めます。
  2. ポップアップ要素は絶対配置、隠蔽指定を行い、コンテンツ用の div 要素を内包させます。
  3. ポップアップ要素の中にこれを閉じるための closeBar を配置しますが、これも絶対配置します。そのバーの右隅に×印があった方がそれらしくなるので、これも絶対配置で配置します。
  4. size インスタンスメソッドを使って、ポップアップ要素等が重複登録されないようにしています。また $ で始まるショートカット変数を指定して要素へのアクセスを簡便化しています。
  5. 引数で closeBar の CSS 値を適切に指定すれば、cloceBar の高さ、色等を変更出来るように、また closeBar 自体を不要とすることも出来るようにしました。但し、その場合には開いたポップアップ要素を閉じるボタン等を作るか、animPopup 要素自身をクリックした時に、hideAnim 関数を起動するように別途設定する必要があります。そのイベントハンドラーは、極めて簡単な例としては、次のようになるでしょう。但し、このコードの場合には収斂位置は指定出来ませんので、animatedPopup を使用した場合に比べると消え方はシンプルになります。
    $("#animPopup").click(function(){$(this).fadeOut()})

▲ToTop

隠蔽アニメーション関数 hideAnim

既に作成した outerShrinkCSS オブジェクトと setShrinkInnerCSS 関数呼び出し結果を第一引数として、2 つの同期的に作動する隠蔽アニメーション関数を定義します。2 つのアニメーションは同時に引き起こされること、待ち行列 fx の登録済みアニメーションを削除してから隠蔽アニメを行う必要があることに留意してください。

ここに setShrinkInnerCSS 関数はコンテンツ要素の CSS 値をシュリンクさせるものです。

また、ポップアップ要素だけでなく、コンテンツ要素にも隠蔽アニメーションを適用していることに注目してください。そうすることにより、ポップアップ要素を隠蔽する際に、そのコンテンツも同じ終点または終線に向かって収斂するようになります。

      // make CSS Object for hide animation
124:  var setShrinkInnerCSS = function(){
125:    var pCSS = this.o.popupCSS,
126:        Xdir = pCSS.origin ===5 || pCSS.origin ===7,
127:        Ydir = pCSS.origin ===6 || pCSS.origin ===8;
128:    return {
129:      width: (Xdir ? this.o.popupSize.cW : 0) +"px",
130:      height: (Ydir ? this.o.popupSize.cH : 0) +"px",
131:      opacity:0
132:    };
133:  }
134:  var hideAnim = function(){ // popup 要素の隠蔽アニメーション
135:    this.o.hidden=true;
136:    $(":animated").queue('fx',[]).stop(); >// 登録済みのアニメを全て停止する
137:    $animPopup.animate(this.o.shrinkOuterCSS,
          {queue:false,duration:this.o.duration,easing:this.o.easing})
          .children().eq(0).animate(innerShrinkCSS.call(this),
          {duration:this.o.duration,easing:this.o.easing});
138:  }
ポップアップ表示アニメーション関数 showAnim

この関数でもポップアップ要素とコンテンツ要素の 2 つを対象にして、同期アニメーションを実行します。関数の起動元は animatedPopup プラグインを起動した jQuery インスタンスです。

139:  var showAnim = function(){ // popup 要素の表示アニメーション
140:    if (errFlag) {
141:      errFlag = false; //$(this).unbind("click");
142:      return errFunc();
143:    }
144:    $(":animated").queue('fx',[]).stop(); // 登録済みのアニメを全て停止する
        // 表示する前に、animPopup の幅と高さをゼロにして所定位置に
        // 非表示描画で配置する。
145:    if (this.o.hidden===undefined)
146:      $animPopup.css(this.o.shrinkOuterCSS).children().eq(0).css(innerShrinkCSS);
        // 表示アニメーション
147:    this.o.popupCSS.display="block";
148:    var fn = function(){$.fn.animatedPopup.running=false;} // 初期化
149:    $animPopup.animate(this.o.popupCSS,{
150:      queue:this.o.queue, duration:this.o.duration,
151:      easing:this.o.easing, complete:this.o.complete}
152:    ).children().eq(0).animate(
153:      {width:this.o.popupCSS.width,height:this.o.popupCSS.height,opacity:this.o.popupCSS.opacity},
154:      {queue:false,duration:this.o.duration,easing:this.o.easing}
155:    );
156:  }
  1. 実行に先立ち、もしエラーがあれば、errFunc 関数を起動して return 値を return して、コード進行から抜け出します。二度の return が必要なのは showAnim メソッドがトップレベルではなく、doAnim メソッド内から呼び出されるためです。なお、jQuery インスタンスから click バインドを外すことも検討しましたが、一度きりのエラーの alert 表示だけでは不十分と考え、アンバインドは無効としました。(140~143 行)
  2. 次に、待ち行列に登録されている全てのアニメーションの登録を解除します。そうしないと アニメ起動時に、待ち行列に登録されている他の同期アニメーションも起動してしまうからです。(144 行)
  3. 既に隠蔽アニメーションが起動済みの時には、隠蔽 CSS 設定を行う必要がないため、o.hidden プロパティ値を検査します。未だ一度も隠蔽アニメーションが起動していなければ、つまり初めての表示アニメーションである場合には、ポップアップ要素に隠蔽 CSS 値を設定して、表示アニメ起動前のポップアップ要素の状態を作ります。(145~147 行)
  4. 148 行で display 値を block にすることで、表示アニメーションによってポップアップ要素が実際に表示されるようにします。
  5. 後はポップアップ要素とコンテンツに対する 2 つの同期アニメーションを起動するだけです。なお、ここに 2 つの要素をアニメ対象としたのは、コンテンツの器であるポップアップ要素だけでなく、そのコンテンツにもアニメを適用することにより、アニメーションの視覚効果をより一層高めるためです。(149~155 行)

▲ToTop

表示アニメーションの起動 doAnim

初期値は false である running プロパティ値を true にして、アニメーションが始まることを記録します。タイマー変数を解除してから、表示アニメーション関数 showAnim を起動します。この関数も呼び出し元は jQuery インスタンスです。

157:  var doAnim = function(){
158:    $.fn.animatedPopup.running=true;
159:    if (wait) {clearInterval(wait);wait=null};
160:    showAnim.call(this);
161:  }
DOM Ready 関数

これまでの長い過程を経てやっと animatedPopup プラグインの最後の関数に辿り着きました。最後の関数である DOM Ready 関数の中で、これまで説明してきた全ての関数の起動が行われる、と言っても過言ではありません。

163:  $(function(){
164:    makePopupElem();
165:    return jQ.each(function(){
166:      var that=this, // this=jQ[i]
          // animPopup を目的の位置に配置するCSSオブジェクトを作る。
167:      if ($xMark.css("display")!=="none")
168:        $xMark.click(function(){hideAnim.call(that)});
169:      else $animPopup.click(function(){hideAnim.call(that)});
170:      that.o = makeCSS.call(that);
171:      getSize.call(that);
172:      setPos.call(that);
173:      that.o.shrinkOuterCSS = setShrinkOuterCSS.call(that);
174:      if ($.fn.animatedPopup.running){
175:        wait = setInterval(function(){doAnim.call(that)},1000);
176:      } else doAnim.call(that);
177:      $(window).scroll(function(){
178:        this.endScrollPos = $.F.getScroll(window);
179:        that.o.popupCSS.left = parseInt(that.o.popupCSS.left)
180:          + this.endScrollPos.L - this.startScrollPos.L + "px";
181:        that.o.popupCSS.top = parseInt(that.o.popupCSS.top)
182:          + this.endScrollPos.T - this.startScrollPos.T + "px";
183:        that.o.shrinkOuterCSS = setShrinkOuterCSS.call(that);
184:        $animPopup.css({left:that.o.popupCSS.left,top:that.o.popupCSS.top});
185:        this.startScrollPos = this.endScrollPos;
186:      });
187:      $(window).resize(function(){
188:        this.endWinSize = $.F.getSize(window),
189:          x = that.o.initPopupCSS.left==="center" && 2 ||
                  that.o.initPopupCSS.right!==undefined && 1 || false,
190:          y = that.o.initPopupCSS.top==="center" && 2 ||
                  that.o.initPopupCSS.bottom!==undefined && 1 || false;
191:        if (x){
192:          that.o.popupCSS.left = parseInt(that.o.popupCSS.left)
193:            + this.endWinSize.cW/x - this.startWinSize.cW/x + "px";
194:        }
195:        if (y){
196:          that.o.popupCSS.top = parseInt(that.o.popupCSS.top)
197:            + this.endWinSize.cH/y - this.startWinSize.cH/y + "px";
198:        }
199:        that.o.shrinkOuterCSS = setShrinkOuterCSS.call(that);
200:        $animPopup.css({left:that.o.popupCSS.left,top:that.o.popupCSS.top});
201:        this.startWinSize = this.endWinSize;
202:      });
203:    });
204:  }); // End of "DOMReady function"
205:  arguments.callee.hideAnim = function(){hideAnim.call(jQ[0]);};
206;  return jQ;
207:}; // End of "animatedPopup function"
アニメーション起動前の各種準備とアニメ起動
  1. 何はさておき、animatedPopup プラグイン起動直後に、ポップアップ要素や closeBar 要素を頁内に設置します。(163 行)
  2. 165 行以下は jQuery インスタンスが複数のタグ要素を含んでいることを前提にしています。また、jQuery インスタンスに登録されている要素を指し示すために this を多用しますが、参照先が随時変動する this の悪影響を受けぬよう、変数 that に this を代入して利用します。こうしておけば関数内で this がグローバルオブジェクト(つまり window オブジェクト)を参照してしまっても、that によって意図したとおり確実に jQuery インスタンスに登録されている要素を指し示すことが出来ます。(166 行)
  3. 諸関数の起動前に closeBar に対する click イベントを登録します。(163行)
  4. その後順次、makeCSS、getSize、setPos、setShrinkOuterCSS を起動し、ポップアップ要素のアニメーションに必要となる表示状態と隠蔽状態の各 CSS 値設定を行います。(170~173 行)なお、全ての関数が call メソッドを適用して呼び出されていることに注目してください。また、呼び出された関数内では this キーワードが起動元を参照することを積極的に活用していることにも注目してください。
  5. 全ての設定を終えた後に、174~176 行でアニメーション起動を行います。

▲ToTop

アニメーション起動後のイベント登録

ポップアップ要素のアニメーションが終わってから、スクロールすることもあれば、window サイズを変更することもあり得ます。これらに対応出来るように、window オブジェクトに 2 つのイベントを登録しました。

まずスクロールイベントから。

このスクロールイベントはポップアップ後に左右上下のスクロールが発生した場合、表示済みのポップアップ要素を window 内での「初期表示位置」に保つことを目的とします。初期表示位置とは、引数 opts で指定した位置( 指定しない場合には、初期値で指定してある window 座標の top:"center",left:"center" )です。

このイベントハンドラーにより、例えば、画面右下に配置指定を行って animatedPopup プラグインを起動した場合、スクロールイベントが発生しても、当該のポップアップ要素はずっと画面右下に配置され続けるようにします。

  1. 最初にスクロールイベント発生後の頁座標上の top 値、left 値を this.endScrollPos オブジェクトのプロパティに取得します。(178 行)
  2. 次に、このオブジェクトのプロパティ値と、スクロールイベント発生前に作成済みの startScrollPos オブジェクトのプロパティ値の差分値を算出し、top 値と left 値を補正します(179~182 行)。この差分値がスクロール量になります。
  3. 補正した top 値、left 値をポップアップ要素の隠蔽用 CSS セットと表示用 CSS セットに上書きし、それにより表示済みポップアップ要素の位置を変化させ、隠蔽アニメーションの収斂点や収斂線を移動します。(183~184 行)
  4. 最後にスクロールイベント発生後の top 値、left 値をスクロールイベント発生前の値に代入し、更なるスクロールイベントの発生に備えます。

次はwindow リサイズイベントです。

このリサイズイベントはポップアップ後に window サイズが変更された場合、表示済みのポップアップ要素を window 内での「初期表示位置」に保つことを目的とします。初期表示位置とは、引数 opts で指定した位置( 指定しない場合には、初期値で指定済みの top:"center",left:"center" )です。

スクロールイベントの場合には全ての配置形式において、配置位置を変更させるようにコーディングしましたが、window リサイズイベント発生時に要素配置を変更させる意味があるのは、センター配置と right プロパティや bottom プロパティが指定された場合のみと考えました。top 値、left 値が値で指定された場合には、リサイズしても位置を変更させる必要はありません。

このイベントハンドラーにより、例えば、画面横及び縦方向の中心位置にポップアップ要素を配置するように指定して animatedPopup プラグインを起動した場合、window サイズを変更しても当該のポップアップ要素は引き続き画面横及び縦方向の中心位置に配置され続けます。

  1. 最初に、リサイズイベント発生直後の window サイズを取得し this.endWinSize オブジェクトのプロパティに記録します。
  2. また配置指定が center で行われた場合には x や y に 2 を代入し、right や bottom 指定が行われた場合には x や y に 1 を代入します。x や y はリサイズ時に必要な補正を行うための変数です。
  3. その補正を 191~198 行で行います。
  4. その後、表示及び隠蔽アニメーション用 CSS セットに補正値を代入し、配置位置を決めます。(199~200 行)
  5. リサイズ直後の配置位置オブジェクトを更なるwindowリサイズに備えて、this.startWinSize オブジェクトに代入します。(201 行)

▲ToTop

プラグイン関数に、よく使うその内部関数を登録する

これはふと思いついたにしては、大変効果が大きく素晴らしいと思っているコードです。

プラグイン関数の中で定義した、プラグイン内部だけで使用する関数を、当該プラグインが起動し終えた後でも、つまりコード進行が終わった後でも使用したい場合にはどうすればよいだろうか?───それが事の発端です。

特にポップアップ要素の場合、随時任意に閉じられることが重要で、jQuery インスタンスメソッドである animatedPopup 関数が起動を終えてから、隠蔽関数 hideAnim を起動させる方法はないものか...と考えた結果、ふと思いついたものです。

205 行の arguments.callee.hideAnim = function(){hideAnim.call(jQ[0])}; がそれです。animatedPopup 関数内部におかれた arguments.callee は、animatedPopup 関数それ自体を指し示しますから、そのプロパティに hideAnim を登録し、hideAnim が起動するコードと同様のコードを function 内に記述したのです。

こうすることにより、animatedPopup 関数が終了した後でも、$("#animPopup").animatedPopup.hideAnim() を起動すれば、収斂点や収斂線の指定はそのままに、設計通りの隠蔽アニメーションが起動出来るのです。

これを使用すれば、closeBarCSS を表示しない引数指定を行った場合に、次のように、#animPopup 要素にクリックイベントを登録することによって、隠蔽アニメーションを意図したとおりに起動させることが出来ます。

$("#animPopup").click(function(){$(this).animatedPopup.hideAnim()});

引数の初期値オブジェクト $.fn.animatedPopup.opts

初期値をこの形式で設定する方法は、cycle プラグインに拠りました。非常に重宝するので必ず使用している程使いやすいものです。プラグイン関数の opts プロパティにオブジェクトを代入して設定します。

    // 引数の初期値を設定
208:$.fn.animatedPopup.opts = {
209:  content:"",
210:  popupOffset:{left:"16px",top:"16px"},
211:  popupCSS:{
212:    position:"absolute",zIndex:1000,left:"center",top:"center",
213:    color:"white",fontWeight:"bold",
214:    width:"400px",backgroundColor:"royalblue", margin:0,
215:    paddingTop:"5px",paddingBottom:"5px",paddingLeft:"5px",paddingRight:"5px",
216:    borderWidth:"5px", borderColor: "plum", borderStyle:"ridge",
217:    textAlign:"center", display:"none",
218:    opacity:1, overflow:"visible",
        // 0:center of element,1:leftTop,2:rightTop,3:rightBottom
        // 4:leftBottom,5:topEdge,6:rightEdge,7:bottomEdge,8:leftEdge
219:    origin:0
220:  },
221:  closeBarCSS:{
222:    position:"absolute",zIndex:"1002",
223:    textAlign:"center",
224:    opacity:0.75,top:0,left:0,cursor:"pointer",
225:    fontSize:"small",lineHeight:"1.2em",width:"100%",
226:    backgroundColor:"midnightblue",display:"block"
227:  },
228:  queue:true, duration:800, easing:"swing", complete:function(){}
229:};
230:})(jQuery);
  1. content は何も指定せず、引数でこれを指定しない場合にはエラー関数が起動するようにしました。
  2. popupOffset プロパティはマウスカーソル位置をクリックした位置から、どの程度離すかを定めたものです。初期値は右及び下に 16px 離す設定です。
  3. popupCSS プロパティは animatedPopup によって起動するポップアップ DIV 要素の CSS 値を定めるものです。
  4. closeBarCSS はポップアップ DIV 要素の上部に表示するボックス隠蔽機能を持った要素のCSSを設定するものです。
  5. 228 行は animate メソッドの第 2 引数オブジェクトのプロパティとなる各種初期値です。

animatedPopup 全コード

  1:(function($){
  2:$.fn.animatedPopup = function(content,opts){
  3:  var jQ=this,errFlag=false,wait,ival,$animPopup,$xMark,
  4:      errFunc = function(){errFlag=true; return false;};
  5:  var makeCSS = function(){
  6:    this.startScrollPos = $.F.getScroll(window);
  7:    this.startWinSize = $.F.getSize(window);
        // 入力エラー及び help 対応
  8:    if (opts && opts.constructor!== Object ){
  9:      alert("配置はオブジェクト形式で指定してください。\n
            例1 popupCSS:{left:\"10px\",top:\"100px\"}\n
            例2 popupCSS:{right:\"20px\",bottom:\"100px\"}");
 10:      errFunc();
 11:    };
 12:    if (!content || content==="") {
 13:      alert("ポップアップする内容が指定されていません。");
 14:      errFunc();
 15:    }
 16:    if (content==="?" || content==="?"){
 17:      var text ="jQuery(anyElement).animatedPopup(contents,opts);\n";
 18:      text +="contents: HTML strings\n";
 19:      text +="opts: Object \n";
 20:      text +="{ popupCSS:{CSS styles set of animatedPopup Element},\n";
 21:      text +="  closeBarCSS:{CSS styles set of closeBar Element},\n";
 22:      text +="  duration:time, easing:name, queue:true or false,complete:function\n";
 23:      text +="}";
 24:      alert(text);
 25:      errFunc();
 26:    }
        // popup 用の各種 CSS オブジェクトを作る。
 27:    var ret= $.extend({}, $.fn.animatedPopup.opts, {content:content}, opts && opts.constructor !== Object && {} || opts);
        // ret のサブオブジェクトが引数 opts により上書きされて
        // しまっている場合があるので、改めて拡張する
 28:    ret.popupCSS = $.extend({}, $.fn.animatedPopup.opts.popupCSS, ret.popupCSS);
 29:    ret.popupOffset = $.extend({}, $.fn.animatedPopup.opts.popupOffset, ret.popupOffset);
 30:    ret.closeBarCSS = $.extend({}, $.fn.animatedPopup.opts.closeBarCSS, ret.closeBarCSS);
 31:    ret.initPopupCSS = $.extend({},ret.popupCSS);
 32:    return ret;
 33:  }
 34:
 35:  var getCloseBarHeight = function(){
 36:    $animPopup.css({display:"block",visibility:"hidden"});
 37:    var temp = $xMark.outerHeight(true);
 38:    $animPopup.css({display:"none",visibility:"visible"});
 39:    return temp;
 40:  }
 41:  var getSize = function(){
 42:    var ret = this.o, pCSS = $.extend({},ret.popupCSS);
        // popup 要素にコンテンツを挿入し、popup 要素の width と height を計測
        // 1.right や bottom が指定された場合高さを正確に測れなくなるので、
        // これらの属性を削除する。
 43:    if (pCSS.right !== undefined) delete pCSS.right;
 44:    if (pCSS.bottom !== undefined) delete pCSS.bottom;
        // 2.既定の幅と高さをキャンセル後に
        // $animPopup's width を計測し、コンテンツを挿入する。
 45:    $animPopup.css({width:null,height:null,top:0,left:0})
 46:      .children().eq(0).css({width:null,height:null}).html(ret.content);
 47:    var cW = $animPopup.width();
        // 3.幅値を最適化する
 48:    cW = ret.content.match(/\ssrc/) ? cW : Math.min(cW,parseInt(pCSS.width));
        // 4.padding 値を調整する
 49:    pCSS.paddingTop = getCloseBarHeight() + parseInt(pCSS.paddingTop) + "px";
        // 5.得られた幅値を適用して隠蔽状態のまま
        // $animPopup.css メソッドを実行し、各種サイズを計測・取得する
 50:    ret.popupSize = $.F.getSize($animPopup.css($.extend(pCSS,{width:cW+"px"}))[0]);
        // 6.コンテンツが画像などではなく、かつ、幅か高さが 600 px 以上
        // の場合にはスクロールバーを配置する
 51:    if (!ret.content.match(/\ssrc/) && (ret.popupSize.cW >600 || ret.popupSize.cH >600)) ret.popupCSS.overflow="auto";
        // 7.取得した padding 値をポップアップ要素表示 CSS に設定する
 52:    ret.popupCSS.paddingTop = pCSS.paddingTop;
        // 8.取得したコンテンツサイズをポップアップ要素表示 CSS に設定する
 53:    ret.popupCSS.width = $animPopup.children().eq(0)[0].style.width = ret.popupSize.cW +"px";
 54:    ret.popupCSS.height = $animPopup.children().eq(0)[0].style.height = ret.popupSize.cH +"px";
 55:  }
 56:  var setPos = function(){
 57:    var pCSS = this.o.popupCSS, offCSS = this.o.popupOffset, pSize = this.o.popupSize;
        // right や bottom 指定がされた場合
 58:    if (pCSS.right !== undefined) {
 59:      pCSS.left = this.startWinSize.cW - pSize.mW - parseInt(pCSS.right) +"px";
 60:      delete pCSS.right;
 61:    }
 62:    if (pCSS.bottom !== undefined){
 63:      pCSS.top = this.startWinSize.cH - pSize.mH - parseInt(pCSS.bottom) +"px";
 64:      delete pCSS.bottom;
 65:    }
        // left 値や top 値が "center" で指定された場合
 66:    if (pCSS.left ==="center") pCSS.left = this.startWinSize.cW/2 - pSize.mW/2+"px";
 67:    if (pCSS.top ==="center") pCSS.top = this.startWinSize.cH/2 - pSize.mH/2+"px";
        // mouse cursor 近傍にpopupする場合の位置指定
        // left 値か top 値に false を指定することによりその旨を指定する
 68:    if (pCSS.left === false || pCSS.top === false) {
 69:      pCSS.left = parseInt($.F.mousePos.X) + parseInt(offCSS.left) + "px";
 70:      pCSS.top = parseInt($.F.mousePos.Y) + parseInt(offCSS.top) +"px";
 71:    } else {
        // mouse cursor 近傍以外の場所に表示する場合には scroll 値を加算
 72:      pCSS.left=parseInt(pCSS.left) + this.startScrollPos.L+"px";
 73:      pCSS.top=parseInt(pCSS.top) + this.startScrollPos.T+"px";
 74:    }
        // 画面外への飛び出し防止補正
 75:    pCSS.left = Math.max(0, Math.min(this.startWinSize.cW + this.startScrollPos.L - pSize.mW, parseInt(pCSS.left)))+"px";
 76:    pCSS.top = Math.max(0, Math.min(this.startWinSize.cH + this.startScrollPos.T - pSize.mH, parseInt(pCSS.top)))+"px";
 77:  }
      // 幅/高さが極小の要素 css 値を設定する。これにより展開/縮小を演出する。
 78:  var setShrinkOuterCSS = function(){
 79:    var pCSS = this.o.popupCSS,
 80:        pSize = this.o.popupSize,
 81:        Xdir = pCSS.origin ===5 || pCSS.origin ===7,
 82:        Ydir = pCSS.origin ===6 || pCSS.origin ===8;
 83:    return {opacity:0,
 84:      borderLeftWidth: Xdir ? pCSS.borderLeftWidth : 0,
 85:      borderRightWidth: Xdir ? pCSS.borderRightWidth : 0,
 86:      borderTopWidth: Ydir ? pCSS.borderTopWidth : 0,
 87:      borderBottomWidth:  Ydir ? pCSS.borderBottomWidth : 0,
 88:      paddingLeft: Xdir ? pCSS.paddingLeft : 0,
 89:      paddingRight: Xdir ? pCSS.paddingRight : 0,
 90:      paddingTop: Ydir ? pCSS.paddingLeft : 0,
 91:      paddingBottom: Ydir ? pCSS.paddingBottom : 0,
 92:      marginLeft: Xdir ? pCSS.marginLeft : 0,
 93:      marginRight: Xdir ? pCSS.marginRight : 0,
 94:      marginTop: Ydir ? pCSS.marginTop : 0,
 95:      marginBottom: Ydir ? pCSS.marginBottom : 0,
 96:      width: (Xdir ? pSize.cW : 0) +"px",
 97:      height: (Ydir ? pSize.cH : 0) +"px",
 98:      width: ((pCSS.origin ===5 || pCSS.origin ===7) ? pSize.cW : 0) +"px",
 99:      height: ((pCSS.origin ===6 || pCSS.origin ===8) ? pSize.cH : 0) +"px",
100:      left:parseInt(pCSS.left) + 
101:        (pCSS.origin ===0 ? pSize.mW/2 :
102:        pCSS.origin ===2 ? pSize.mW :
103:        pCSS.origin ===3 ? pSize.mW :
104:        pCSS.origin ===6 ? pSize.mW : 0) +"px",
105:      top:parseInt(pCSS.top) +
106:        (pCSS.origin ===0 ? pSize.mH/2 :
107:        pCSS.origin ===3 ? pSize.mH :
108:        pCSS.origin ===4 ? pSize.mH :
109:        pCSS.origin ===7 ? pSize.mH : 0)+"px"
110:    }
111:  }
112:
      // popup 要素及び幅事前計測用の div 要素タグの作成
113:  var makePopupElem = function(){
114:    if (!$("#animPopup").size()) {
115:      $("<div id='animPopup'></div>")
            .css({position:"absolute",display:"none",zIndex:"1000"})
            .append("<div></div>").appendTo(document.body);
116:    }
117:    $animPopup=$("#animPopup");
        <span class="aquamarine">// Popup 隠蔽用×タグの作成</span>
118:    if (!$("#xMark").size()){
119:      $("<div id='xMark'>CLOSE</div>")
            .css($.fn.animatedPopup.opts.closeBarCSS)
            .append("<div style='position:absolute;z-index:1003;top:0;right:2px;width:13px'>×</div>")
            .appendTo($animPopup);
120:    }
121:    $xMark = $("#xMark");
122:  };
      // for hide animation
123:  var setShrinkInnerCSS = function(){
124:    var pCSS = this.o.popupCSS,
125:        Xdir = pCSS.origin ===5 || pCSS.origin ===7,
126:        Ydir = pCSS.origin ===6 || pCSS.origin ===8;
127:    return {
128:      width: (Xdir ? this.o.popupSize.cW : 0) +"px",
129:      height: (Ydir ? this.o.popupSize.cH : 0) +"px",
130:      opacity:0
131:    };
132:  }
133:  var hideAnim = function(){ // popup 要素の隠蔽アニメーション
134:    this.o.hidden=true;
135:    $(":animated").queue('fx',[]).stop(); >// 登録済みのアニメを全て停止する
136:    $animPopup.animate(this.o.shrinkOuterCSS,
          {queue:false,duration:this.o.duration,easing:this.o.easing})
          .children().eq(0).animate(setShrinkInnerCSS.call(this),
          {duration:this.o.duration,easing:this.o.easing});
137:  }
138:
139:  var showAnim = function(){ // popup 要素の表示アニメーション
140:    if (errFlag) {
141:      errFlag = false; //$(this).unbind("click");
142:      return errFunc();
143:    }
144:    $(":animated").queue('fx',[]).stop(); // 登録済みのアニメを全て停止する
        // 表示する前に、animPopup の幅と高さをゼロにして
        // 所定位置に非表示描画で配置する。
145:    if(this.o.hidden===undefined)
146:      $animPopup.css(this.o.shrinkOuterCSS).children().eq(0).css(setShrinkInnerCSS.call(this));
        // 表示アニメーション
147:    this.o.popupCSS.display="block";
148:    var fn = function(){$.fn.animatedPopup.running=false;} // 初期化
149:    $animPopup.animate(this.o.popupCSS,{
150:      queue:this.o.queue, duration:this.o.duration,
151:      easing:this.o.easing, complete:this.o.complete}
152:    ).children().eq(0).animate(
153:      {width:this.o.popupCSS.width,height:this.o.popupCSS.height,opacity:this.o.popupCSS.opacity},
154:      {queue:false,duration:this.o.duration,easing:this.o.easing}
155:    );
156:  }
157:  var doAnim = function(){
158:    $.fn.animatedPopup.running=true;
159:    if (wait) {clearInterval(wait);wait=null};
160:    showAnim.call(this);
161:  }
162:
163:  $(function(){
164:    makePopupElem();
165:    jQ.each(function(){
166:      var that=this, // this=jQ[i]
          // animPopup を目的の位置に配置するCSSオブジェクトを作る。
167:      if ($xMark.css("display")!=="none")
168:        $xMark.click(function(){hideAnim.call(that)});
169:      else $animPopup.click(function(){hideAnim.call(that)});
170:      that.o = makeCSS.call(that);
171:      getSize.call(that);
172:      setPos.call(that);
173:      that.o.shrinkOuterCSS = setShrinkOuterCSS.call(that);
174:      if ($.fn.animatedPopup.running){
175:        wait = setInterval(function(){doAnim.call(that)},1000);
176:      } else doAnim.call(that);
177:      $(window).scroll(function(){
178:        this.endScrollPos = $.F.getScroll(window);
179:        that.o.popupCSS.left = parseInt(that.o.popupCSS.left)
180:          + this.endScrollPos.L - this.startScrollPos.L + "px";
181:        that.o.popupCSS.top = parseInt(that.o.popupCSS.top)
182:          + this.endScrollPos.T - this.startScrollPos.T + "px";
183:        that.o.shrinkOuterCSS = setShrinkOuterCSS.call(that);
184:        $animPopup.css({left:that.o.popupCSS.left,top:that.o.popupCSS.top});
185:        this.startScrollPos = this.endScrollPos;
186:      });
187:      $(window).resize(function(){
188:        this.endWinSize = $.F.getSize(window),
189:          x = that.o.initPopupCSS.left==="center" && 2 ||
                  that.o.initPopupCSS.right!==undefined && 1 || false,
190:          y = that.o.initPopupCSS.top==="center" && 2 ||
                  that.o.initPopupCSS.bottom!==undefined && 1 || false;
191:        if (x){
192:          that.o.popupCSS.left = parseInt(that.o.popupCSS.left)
193:            + this.endWinSize.cW/x - this.startWinSize.cW/x + "px";
194:        }
195:        if (y){
196:          that.o.popupCSS.top = parseInt(that.o.popupCSS.top)
197:            + this.endWinSize.cH/y - this.startWinSize.cH/y + "px";
198:        }
199:        that.o.shrinkOuterCSS = setShrinkOuterCSS.call(that);
200:        $animPopup.css({left:that.o.popupCSS.left,top:that.o.popupCSS.top});
201:        this.startWinSize = this.endWinSize;
202:      });
203:    });
204:  }); // End of "DOMReady function"
205:  arguments.callee.hideAnim = function(){hideAnim.call(jQ[0]);};
206;  return jQ;
207:}; // End of "animatedPopup function"
    // 引数の初期値を設定
208:$.fn.animatedPopup.opts = {
209:  content:"",
210:  popupOffset:{left:"16px",top:"16px"},
211:  popupCSS:{
212:    position:"absolute",zIndex:1000,left:"center",top:"center",
213:    color:"white",fontWeight:"bold",
214:    width:"400px",backgroundColor:"royalblue", margin:0,
215:    paddingTop:"5px",paddingBottom:"5px",paddingLeft:"5px",paddingRight:"5px",
216:    borderWidth:"5px", borderColor: "plum", borderStyle:"ridge",
217:    textAlign:"center", display:"none",
218:    opacity:1, overflow:"visible",
        // 0:center of element,1:leftTop,2:rightTop,3:rightBottom
        // 4:leftBottom,5:topEdge,6:rightEdge,7:bottomEdge,8:leftEdge
219:    origin:0
220:  },
221:  closeBarCSS:{
222:    position:"absolute",zIndex:"1002",
223:    textAlign:"center",
224:    opacity:0.75,top:0,left:0,cursor:"pointer",
225:    fontSize:"small",lineHeight:"1.2em",width:"100%",
226:    backgroundColor:"midnightblue",display:"block"
227:  },
228:  queue:true, duration:800, easing:"swing", complete:function(){}
229:};
230:})(jQuery);

複数の jQuery プラグインで共有するオブジェクトを無名関数内に定義する

複数の jQuery プラグインを管理するために必要となる共通関数

マウスカーソル位置、要素位置、要素サイズ、window サイズ、スクロール値などの値は、jQuery プラグインを作成する上で、あれこれの複数のプラグインに共通して必要となる基本的な情報であり、どのプラグインからも使用できるように定義することが求められる。

こうして複数のプラグインから共通して利用できるオブジェクトを作成した。

概要は以下の通り。

  1. 各プラグインから共通して使用する jQuery クラスオブジェクト F を、任意の 1 以上のプラグインコードが記されたファイル内の無名関数内に配置する。
  2. プラグインファイルが 2 つ以上に分割されていても利用できるようにする。
  3. F のプロパティには HTML 要素や winodw の位置、大きさ、スクロール値を算出する関数を登録する。
  4. また、window resize、window scroll 及び mousemove イベントの各ハンドラーも F のプロパティに登録する。
  5. イベントハンドラーを除く上記の諸関数による取得値を、各プラグインコード内の変数や、HTML 要素を参照するオブジェクト( jQuery インスタンスオブジェクトを含む )に登録できるように、諸関数の引数を工夫する。
  6. F オブジェクトには getPos、getSize、getScroll、eF、winSize、winscrollPos、mousePos の 8 つのプロパティを設け、各プロパティ値を全て関数か、関数の実行返値とする。
  7. getPos、getSize、getScroll の 3 つの関数は、eF 関数オブジェクトから、あるいは各プラグインコード内に設けられた DOM Reaady 関数内から直接び出され、これらに返値を返す。
  8. 複数のプラグインから利用することを想定しているイベントハンドラーは、試行錯誤の結果、各プラグインコード内の変数や、HTML 要素を参照するオブジェクトに登録するのではなく、直接 $.F オブジェクトのプロパティ値として登録することにした。
  9. 言わずもがなのことであるが、mousemove イベントは DOM が Ready された状態でマウスカーソルが移動することによって発生する。そのため例えば DOM が Ready される前に、マウスカーソルを当該ブラウザとは別のアプリケーションに移動した場合には $.F.mousePos オブジェクトが誕生する前に、それを利用するコードが進行する場合もある。このような場合に対処するために、$.F.mousePos = {X:"0px",Y:"0px"};の一行を挿入した。

その jQuery クラスオブジェクトについて

オブジェクト名は単純明快に F とし、次のように定義した。因みに jquery.js による jQuery クラスオブジェクトには F プロパティは存在しない。

  $.F ={
    // 要素の位置計測
    getPos : function(elem){
      if (!$(elem)||!$(elem).offset()) return false;
      var off = $(elem).offset(),
      	  pos = $(elem).position();
      return {
        left : off.left, // 絶対座標横方向値
        top : off.top, // 絶対座標縦方向値
        posLeft : pos.left, // offsetParent座標横方向値
        posTop : pos.top // offsetParent座標横方向値
      }
    },
    // 要素の大きさ計測
    getSize : function(elem){
      if (!$(elem)||!$(elem).width()) return false;
      if (elem===window){
        return { //内容辺サイズ、border 辺サイズ
          cW:$(elem).width(), cH:$(elem).height(),
          oW:$(elem).outerWidth(), oH:$(elem).outerHeight()
        }
      } else {
        return { //内容辺サイズ、padding 辺サイズ、border 辺サイズ及び margin 返サイズ
          cW:$(elem).width(), cH:$(elem).height(),
          iW:$(elem).innerWidth(), iH:$(elem).innerHeight(),
          oW:$(elem).outerWidth(), oH:$(elem).outerHeight(),
          oMW:$(elem).outerWidth(true), oMH:$(elem).outerHeight(true)
        }
      }
    },
    // スクロール値取得
    getScroll : function(elem){
      if (!$(elem)) return false;
      return {L:$(elem).scrollLeft(), T:$(elem).scrollTop()}
    },
    // ノードに位置、サイズ及びスクロールの各プロパティを設定する。
    eF : function(elem){
      if (!$(elem)) return false;
      elem.pos = $.data(elem,"pos") || $.data(elem,"pos",$.F.getPos(elem));
      elem.size = $.data(elem,"size") || $.data(elem,"size",$.F.getSize(elem));
      elem.scroll =$.data(elem,"scroll") || $.data(elem,"scroll",$.F.getScroll(elem));
    }
  }
  // $.F オブジェクトにwindowサイズとスクロール値及び
  // resize event ハンドラー, scroll event ハンドラーを登録する。
  $(function(){
      $.F.winSize = $.F.getSize(window); // 現在値の取得
      $.F.winScrollPos = $.F.getScroll(window); // 現在値の取得
      $.F.mousePos = {X:"0px",Y:"0px"}; // 初期値設定
      //resize,scroll イベントハンドラー
      $(window).resize(function(){$.F.winSize = $.F.getSize(window)})
        .scroll(function(){$.F.scrollPos = $.F.getScroll(window)});
      // body 上での mousemove イベントハンドラー。マウス位置取得
      $(document.body).mousemove(function(e){
        $.F.mousePos={X:e.pageX, Y:e.pageY};
      });
    }
  })

▲ToTop

各プラグインから共用する F オブジェクトの説明

$.F.eF メソッドにより任意の HTML 要素の位置、大きさおよびスクロール値を取得する。また、$.F オブジェクトの winSize 及び winScrollPos プロパティに、頁オープン時の window の大きさとスクロール値を取得し、かつ、これらの値は window resize 及び window scroll イベントに対応させた。更に $.F.mousePos プロパティに、mousemove イベントによるマウスカーソル位置を取得した。

なお、$.F オブジェクト定義時(つまり頁を開いた時)にマウスカーソルが当該 window 内にない場合もあるので、mousePos プロパティは初期値として {top:0,left:0} を設定した。そうしないとマウスカーソルが window 外にある場合には、$.F オブジェクト定義時に mousePos プロパティが未定義になってしまうからである。

eF メソッドにより取得された値は、対象となった HTML 要素オブジェクトの pos、size 及び scroll プロパティ値に格納される。このことを jQuery インスタンスで表現すれば、jQuery における対象要素表現 strings を expr として、jQuery(expr)[0].pos、jQuery(expr)[0].size、jQuery(expr)[0].scroll である。

DOM Ready 関数内で取得された window サイズ、スクロール値あるいはマウスカーソル絶対座標値は、$.F オブジェクトの winSize、winScrollPos あるいは mousePos プロパティに格納される。

F オブジェクトや複数のプラグインを内包する無名関数の構成

■ 1 以上のプラグインコードを含む 1 つの任意のプラグインファイル内の冒頭で、
  $.F オブジェクトを登録する。
(function($){
  // 複数のプラグインから利用する F オブジェクトを定義する
  $.F={……}; 
  $(function(){
    $.F.winSize = $.F.getSize(window);
    ・・・
    ・・・
    $(document.body).mousemove(function(e){
      $.F.mousePos={X:e.pageX, Y:e.pageY};
    });
  });
  $.fn.pluginA = function(){ // プラグイン A
    // eF 関数を jQuery インスタンスの i 番目の要素を引数にして起動。これにより、
    // 当該要素の絶対位置とオブセットペアレント内の位置が当該要素の pos プロパティに、
    // 当該要素の大きさが当該要素の size プロパティに、
    // 当該要素のスクロール値が当該要素の scroll プロパティに、それぞれ設定される。
    $.F.eF(this[i]);
    ・・・・・・・
  };
  $.fn.pluginB = function(){ // プラグイン B
    $.F.eF(this[i]);
	・・・・・・・
  };
  ・・・・・・・
})(jQuery)

■ 別のプラグインファイル内からの $.F オブジェクトの利用
(function($){
  $.fn.pluginC = function(){ // プラグイン C
    // eF 関数を jQuery インスタンスの i 番目の要素を引数にして起動。これにより、
    // 当該要素の絶対位置とオブセットペアレント内の位置が当該要素の pos プロパティに、
    // 当該要素の大きさが当該要素の size プロパティに、
    // 当該要素のスクロール値が当該要素の scroll プロパティに、それぞれ設定される。
    $.F.eF(this[i]);
    ・・・・・・・
  };
  $.fn.pluginD = function(){ // プラグイン D
    $.F.eF(this[i]);
	・・・・・・・
  };
  ・・・・・・・
})(jQuery)

jQuery を使って画像も動画も Popup する

popup メソッドは2年半前に一度作成していた

2007年12月。PopupTips と題して、jquery.js を最小限度使用してポップアップを作りました。お陰様で 6 件の拍手を戴きましたが、パソコンの高速化がますます定着し、加速されている今、jquery.js をフル活用したコードでも、描画に大きな支障は出ない程度にまでパソコン性能が向上しました。
だから、というわけでもありませんが、今回改めて jquery.js をフル活用して popup メソッドを作り直しました。

それは上の「関連エントリイ」に掲載した jQuery()活用(2) Popup Tips を自作する──jQuery解読(22) に詳述したので、関心のある方はそちらをご覧戴ければ幸いです。

jquery.js をフル活用して作成した popup メソッドの概要

ここでは自作の setPopup プラグインメソッドの概要を紹介させて戴きます。なお、以下のアンダーライン部に popup を仕込みましたので、どんな具合に作動するのか、このエントリイで確認して戴くことが出来ます。

  1. 改めて作成した popup は それを引き起こすタグ要素の title 属性値をそのコンテンツとするように設計しました。つまり、何らかの class 指定を行わなくても、jQuery('*[title]').setPopup(args) で popup 励起要素が指定出来るようにしました。
  2. 勿論、特定の要素に class 指定を施して、それらの要素に対してだけ popup を表示させることも可能です。つまり、popup 励起要素はいかようにも自由に指定できるようにしました。
  3. popup 励起要素内で mouseenter/mouseleave イベントが発生すると、popup 要素が表示/隠蔽されることは勿論ですが、更に mousemove イベントが起きると、popup 要素はマウスカーソルに追随して移動するように設計しました。なお、その際には popup 要素が画面からはみ出さないようにもしました。
  4. popup する内容はテキスト文字列だけではなく、任意の HTML タグ要素を指定出来るようにしました。つまり、title 属性値に記述した HTML 文が HTML 文として解釈され、popup 要素内に描画されるようにしました。例えば、番号付き箇条書きや画像を popup させることも可能です。(ここではパノラマ写真を popup します)
  5. 次に動画を popup させてみます。なお動画表示の場合には HTML 文に若干の工夫が必要です。popup 要素内にはマウスカーソルを移動出来ないので、自動再生を指定しておく必要がある、ということです。
    なお、youtube 動画の自動再生指定方法は、 youtubeの動画を自動再生する方法 に大変分かりやすく記されていました。

▲ToTop

どのエントリイでも、どのブログでも使えるように plug-in 化

作成した Javascript コードの全容は こちら に掲載しておきましたが、このコードをプラグイン化したことを強調しておこうと思います。

プラグイン化により、このブログのどのエントリイでも、否、このブログだけではなく、また Fc2 ブログでなくても、任意のブログで使える仕様になっているはずです。

jquery.js をフル活用しつつ、汎用的に利用できる───今後作成するjquery.js を利用したコードは、可能な限りこのような仕様にするつもりです。それこそが jquery.js を真の意味で活用することになるはずですし、そうなればこその「やり甲斐」でもあります。

jQueryを使ってブログエントリイの目次を自動作成する。

この上の目次は以下に述べるスクリプトにより自動作成されたものである。

このエントリイの後に、別のもっとクールな目次自動作成プラグインを作った。このため、このエントリでのみ 2 通りの目次が作成され、表示されるようにした。この他のエントリイでは新しい目次作成プラグインを使用するようにしたため、このエントリイでのみ旧バージョンとなってしまった目次作成プラグインを表示している。

なお、新しい目次自動作成プラグインについては、こちらで 触れている。

エントリイ目次の作成意義

私のブログエントリイは、各エントリイの文章が長くなる傾向にある。

そして長いエントリイの場合には、全体を概観する必要が生じるので目次があった方が良い。

しかも、その目次は或るルールに基づいて作成することにすれば、エントリイ毎に作らなくてもスクリプトで自動作成させることが可能だ。

そこで、jQuery を活用する簡単な例として作ってみた。

エントリイ目次のスクリプトによる作成方法

目次項目はヘッダー要素のコンテンツをピックアップすれば良く、それに頁内リンクを貼った上で箇条書き整形してから、エントリイ内に挿入すればよい。

なお、頁内リンクを貼るということは、リンク先となるヘッダー要素のコンテンツを a タグで包含しなければならず、その行為もスクリプトで自動的に行うべきだろう。

目次自動作成:makeContents メソッドコード

以下に makeContents コードとその各行説明を掲載したが、コードの要点を列挙しておきたい。

  1. makeContents メソッドは汎用性が高いプラグイン形式とした。
  2. このメソッドに 2 つの引数を用意した。これは、初期状態で目次を表示するか否かを指定し、また目次の背景色を自在に変更可能にするためだ。なお、2 つの引数の記述順は指定せず、いずれが先でもインタープリタが「正しく理解」できるよう工夫した。
  3. 初期状態で目次が表示されているか否かに応じて、目次表示/隠蔽ボタンの機能は変化するが、それを 1 つの toggle イベントハンドラーで扱うにあたり、ボタン背景色と slideDown/Up を交互に入れ替えるために配列の reverse メソッドを活用した。
  4. blur メソッドを使ってボタン要素にフォーカスが残らないようにした。
  5. 自作コードで初めて wrapInner メソッドを利用した。これは或るタグ要素内のコンテンツを、新たな子要素で囲む場合に重宝するメソッドだ。或る要素Aの子要素Bを新たな子要素Cを挿入してCの子要素(つまりAの孫要素)にする便利なメソッドである。
  6. $btn オブジェクトに見られるように jQuery インスタンスメソッドチェーンを多用した。メソッドチェーンは余りの便利さに快感すら覚える。これもまた jquery.js の醍醐味と言えよう。
    因みにここでチェーンしたメソッドは、順に .css、.prependTo、.toggle だ。しかもjQuery インスタンスメソッドの返値が jQuery インスタンス自身となることを利用して、チェーンメソッドが適用されている jQuery インスタンスを右辺に配置して、これを左辺の変数に代入することまで一つの式で実現した。
  7. 上で述べた jQuery インスタンスメソッドの返値を利用した式は、他の箇所でも多用している。変数 $contents や $h4 への代入式もまた、 jQuery インスタンスメソッド返値が jQuery インスタンスであることを利用している。
// makeContents メソッドをプラグインとして登録する。
(function ($){$.fn.makeContents = function(display,color){
  // 個別エントリイ表示モード以外の場合何もしない。
  if (!/.+blog-entry.+html$/.test(location.href)) (function(){return;})();
  else { // 個別エントリイ表示モードならば実行する
    $(function(){ // DOM ready イベントに登録する。
    // もし #contents 要素が存在すればそれを削除する。
    if ($("#contents").size()) $("#contents").remove();
    // ローカル変数定義
    var $entry, $contents, $h4, $btn, o = [], bgColor=
      // 目次ブロックの背景色を設定する。
      (typeof display==="boolean" && typeof color==="string" && color) 
      || (typeof color==="boolean" && typeof display==="string" && display)
      || "darkslategray";
    // エントリイ本体部分へのショートカット作成
    $entry = $("div.entry_body");
    // 目次を表示する ol 要素をスタイルシート付きでエントリイ
    // 最上部に挿入する。但し、コード進行が終了間際迄は非表示としておく。
    $contents = $("<ol id='contents' />").css({
      margin:"1em 2em",padding:"0.5em 2em",border:"1px dotted white",
      lineHeight:"1.1em",background:bgColor
    }).prependTo($entry).hide();
    // H4 ヘッダーコンテンツに id を付け、H4 ヘッダーコンテンツを
    // 頁内リンク付きの LI 要素として目次の OL 要素内に挿入する
    $h4 = $("h4",$entry).each(function(i){
      $(this).wrapInner("<a id='contents"+i +"'></a>");
      $contents.append(
        "<li><a href='#contents"+i+"'>"+$(this).text() +"</a></li>"
      );
    });
    // ボタン背景色と slide メソッドを配列に登録
    o[0]={btncolor:"pink",fn:function(){$contents.slideDown()}};
    o[1]={btncolor:"palegreen",fn:function(){$contents.slideUp()}};
    // 目次を最初に表示しない指定の場合、
    // 色と slide メソッドのセットを入れ替える。
    if (!display) o.reverse();

    // 目次の表示/隠蔽を操作するボタンをエントリイ本体上部に配置し、
    $btn = $("<button>目次の表示/隠蔽</button>").css({
      display:"block",background:o[0].btncolor,
      fontWeight:"bold",width:"150px",margin:"0 auto"
    }).prependTo($entry).toggle(function(){ // toggle イベントを登録する。
      // slide メソッドの実行。
      o[1].fn();
      // ボタンの背景色を変更する。
      $(this).css({background:o[1].btncolor}).blur();
    },function(){
      o[0].fn();
      $(this).css({background:o[0].btncolor}).blur();
    });
    // 目次表示モードが指定されている場合には、slideDown メソッドを実行する。
    if (display===true || color===true) $contents.slideDown();
  });}
}})(jQuery);
// makeContents メソッドを目次表示モード( true 指定 )で起動する。
$().makeContents(true);
//$().makeContents(); //エントリイ表示時に目次を非表示にする場合の指定
//$().makeContents(true, "navy"); //エントリイ表示時に目次を表示しその背景色をnavyにする場合の指定

jQuery の各種メソッドを活用して、マウスカーソルの現在位置や要素の位置とサイズなどを取得する

はじめに───ここで行ったこと

このエントリイでは、jQuery の各種メソッドを活用して、まず、マウスカーソルの現在位置の頁座標を取得/表示し、また要素の頁座標と offsetParent 座標値並びに大きさを取得/表示させます。

次に、要素をアニメーションさせた直後に、当該要素の絶対/相対座標値や要素の各サイズ値を取得/表示させます。その際には、値が変わった箇所を一目で分かるように工夫しました。

このアニメーションは要素が往復運動するもので、1の往復運動毎にその動く方向・距離・内容幅・内容高さを乱数を発生させて変動させています。何度か繰り返しアニメーションさせてみると乱数の効果を良く確認出来るでしょう。box2 と box3 に別々の乱数を割り振ったので、「往路」の方向も移動距離も幅と高さの変化率もお互いに異なりますが、復路は必ず最初の位置とサイズに戻るようにプログラミングしました。

また、往路復路の別を分かりやすくするために、往路が終わると要素が半透明になるようにしました。この状態は復路が終わると当初の不透明に戻ります。

testArea...pos:rel
tester1
pos:stat
box1
pos:rel
tester2
pos:rel
box2
pos:abs
tester3
pos:rel
box3
pos:abs
tester4
pos:abs
tester's position
itemAreatester1tester2tester3tester4
Left
Top
posLeft
posTop
width
innerW
outerW
marginW
height
innerH
outerH
marginH
box's position
box1box2box3

※ ピンクの背景色セルは、その値が直前値から変化したことを示す。

▲ToTop

1. マウスカーソルの現在座標値をリアルタイムで取得し表示する

マウスカーソルの現在座標値は、jQuery のイベントに係るメソッドを利用すれば、極めて簡単に取得できます。ブラウザ毎の計測方法の差異は jquery.js が処理してくれるので、後述するように、座標値取得のための javascript コードは極めて簡潔になります。

次に、取得したマウスカーソル座標値の表示については、上の背景色ロイヤルブルーのボックス内にマウスカーソルが入った時にのみ、マウスカーソルの右下にマウスカーソルの頁座標値を表示するようにしました。更に、当該ボックス内でマウスカーソルが移動した場合には、マウスカーソルに追随して座標表示ボックスを移動させ、かつ、刻々と変化する座標値を瞬時に表示するようにしました。

つまり、マウスカーソルの移動に合わせてリアルタイムでその座標値を取得し、座標値の表示場所はマウスカーソルに追随して移動するようにしました。

2. 要素の頁座標値とoffsetParentからの座標値、並びに大きさを取得し表示する

要素の位置と大きさを取得するために利用する jQuery メソッドは次の 10 個です。

offset、position、width、height、
innerWidth/Height、outerWidth/Height、outerWidth(true)/Height(true)

これらの jQuery インスタンスメソッドを使用すれば、極めて容易に要素の位置とサイズが取得出来ます。

jQuery(要素).offset インスタンスメソッドは、対象要素が含まれる表示領域の左辺または上辺から、対象要素のボーダー辺までの横又は縦方向の距離(頁内絶対座標とでも呼ぶべきか?)を計測し、jQuery(要素).position は、対象要素の offsetParent 要素のパディング辺からの、対象要素のマージン辺の横又は縦方向の距離を計測するメソッドです。このことの詳細は、「 jquery.js (1.4) による要素位置の測定と適正な配置 (2) コード解読(1) 」 に詳述しました。

また、width/height、innerWidth/Height、outerWidth/Height及びouterWidth(true)/Height(true)は、順に対峙する内容辺間距離、パッディング辺間距離、ボーダー辺間距離、マージン辺間距離を計測するメソッドです。詳細は拙エントリイ:「 jquery.js (1.4) による要素位置の測定と適正な配置 (5) コード解読 (4) 」を参照してください。画像付きで説明しています。

取得した値は、このエントリイ上部に配置した tester's position 表と box's position 表に表示させました。ここに、Left と Top は頁座標値で offset メソッドを使って、また posLeft と posTop はoffsetParent からの座標値で、position メソッドを使って、それぞれ取得しています。

また、offsettParent の定義から、各 tester ボックスの offsettParent は testAreaとなり、各 box の offsetParent は各 tester ボックスとなります。( 因みに testArea の offsetParent は div#container であり、更に div#container の offsetParent は body となります。body 以外の offsetParent とする要素は、全て CSS スタイル設定で position:relative を指定し、明示的にoffsetParent となるようにしました。)

▲ToTop

3. 要素を移動させるアニメーションと移動後の座標値の取得/表示について

ここで作成したアニメーションは 2 つの div 要素を、ほぼ同時に上 or 下かつ左 or 右に移動させながら、同時に幅と高さを変化させるものです。目的はアニメーションそのものよりも、移動前後の座標値の取得/表示を主眼としましたが、アニメーションそのものにも、少しは興味を引くであろう様々な工夫を凝らしました。(アニメーションの起動はエントリイ上部に配置した animate ボタンをクリックして行います。)

工夫は、単純な移動やサイズ変更の繰り返しでは詰まらないので、乱数 Math.random() メソッドを使って、移動の度に方向・距離・内容サイズが変わるようにしました。easing 関数も乱数によって 11 種類からその都度任意に選択されるようにセットしたので、クリックする度に異なる方向、異なる距離、,異なるサイズ、異なる easing を使ってアニメが展開されます。

また、ここで作成した移動と大きさ変更アニメーションは、往復運動で 1 サイクルになるようにしたので、往路の移動とサイズ変更が終わると要素が半透明になるようにして、往路であることが分かるようにしました。当然ですが復路が終わると位置と大きさは元に戻り、不透明度も 100 %に戻るようにしました。

4. このエントリイのためのスタイルシート

■スタイルシート
#testArea {
  position:relative;background:royalblue;width:520px;padding:1em;/*height:360px;*/
}
.tester {margin:0.5em;width:100px;height:150px;}
#tester1 {border:solid white 2px;} /*position:static*/
#tester2 {position:relative;background:darkred;border:solid yellow 2px;}
#tester3 {position:relative;background:teal;border:solid white 2px;}
#tester4 {position:absolute;top:10px;left:370px;border:solid lime 2px;}
.box {
  position:absolute;top:50px;left:20px;background:indigo;border:solid lime 2px;
  width:66px;height:80px;
}
#box1 {position:relative;}
#result {margin:1em 0;border:1px lightgray dotted;padding:2px;background:dimgray;}
table#table1 input,table#table2 input {width:65px;text-align:right;background:white}
table#table1 caption,table#table2 caption {color:lime}
table#table1 th,table#table2 th {line-height:0.5em;text-align:center}
table#table1 tr,table#table2 tr {line-height:1em;}
button#animBtn {display:block;position:absolute;top:180px;left:400px;z-index:10;}
div#testArea div#log {position:relative;z-index:9;padding-top:4em;line-height:1.1em;}

▲ToTop

5. jQuery の各種メソッドを活用したこのエントリイのための Javascript コードの解説

■javascript code
  // マウスカーソルの座標値を表示するためのdiv要素を作成する。
  $("<div id='testBalloon' />").css({
      position:"absolute",padding:"0.5em",border:"1px solid green",
      display:"none",background:"navy",zIndex:11
  }).appendTo("body");
  var o ={ // 各種 jQuery インスタンスへのショートカットなどを登録するオブジェクト
    $cont: $("#container"),
    $tArea: $("#testArea"),
    $t1: $("#tester1"),  $t2: $("#tester2"),
    $t3: $("#tester3"),  $t4: $("#tester4"),
    $tbl1: $("#table1"), $tbl2: $("#table2"),
    $input: $("input","#result"),  $log: $("#log"),
    $tBalloon: $("#testBalloon"), rnd:{}, // 乱数を格納するオブジェクト
    easing:["swing","easeInOutQuad","easeInOutCubic","easeInOutQuart","easeInOutQuint",
    "easeInOutSine","easeInOutExpo","easeInOutCirc","easeInOutElastic","easeInOutBack",
    "easeInOutBounce"], //easing 関数を登録
    cnt: 0, //カウンター
    flag: true //奇数偶数区別用
  };
  // 各 box へのショートカットを登録する
  o.$b1= $(o.$t1).children(":eq(1)");
  o.$b2= $(o.$t2).children(":eq(1)").css("zIndex","2");
  o.$b3= $(o.$t3).children(":eq(1)").css("zIndex","3");
  // document 内でマウスカーソルが動いた時に、その座標値を o オブジェクトのプロパティに登録する。
  // たったこれだけのコードで目的を達成することが出来る。
  $(document).bind('mousemove',function(e){ 
    o.x = e.pageX;
    o.y = e.pageY;
  });
  // エリア内にマウスカーソルがある時と外れた時の、
  // マウスカーソル座標値を表示するボックスの表示/非表示を制御する。
  o.$tArea.hover(
    function(){o.$tBalloon.fadeIn()},
    function(){o.$tBalloon.fadeOut()}
  ).mousemove(function(){
    // エリア内でマウスカーソルが動いた時に座標値を表示する。
    // 座標値の表示ボックスは、マウスカーソルの右下に 16 pxずれた箇所に表示する。
    o.$tBalloon.html("<div>top: "+ o.y+ "px<br />left: "+o.x+"px</div>")
      .css({top:parseInt(o.y)+16+"px",left:parseInt(o.x)+16+"px"})
  });

  // 要素の位置と大きさを計測する関数を再呼び出し出来るように定義する。
  var doCalc = function(){
    $.each([o.$cont,o.$tArea,o.$t1,o.$t2,o.$t3,o.$t4,o.$b1,o.$b2,o.$b3],function(){
      this.oSet = $(this).offset(); // jQuery.offset メソッド
      this.pos = $(this).position(); // jQuery.position メソッド
      this.left = this.oSet.left; // jQuery.offset().left 値取得
      this.top = this.oSet.top; // jQuery.offset().top 値取得
      this.posLeft = this.pos.left; // jQuery.position().left 値取得
      this.posTop = this.pos.top; // jQuery.position().top 値取得
      this.width = $(this).width(); // 内容辺間の幅取得
      this.innerW = $(this).innerWidth(); // padding 辺間の幅取得
      this.outerW = $(this).outerWidth(); // border 辺間の幅取得
      this.marginW = $(this).outerWidth(true); // margin 辺間の幅取得
      this.height = $(this).height(); // 内容辺間の高さ取得
      this.innerH = $(this).innerHeight(); // padding 辺間の高さ取得
      this.outerH = $(this).outerHeight(); // border 辺間の高さ取得
      this.marginH = $(this).outerHeight(true); // margin 辺間の高さ取得
    });
  }
  doCalc(); //関数実行
  // アニメーションで使用するために、4 つのプロパティの初期値を
  // アニメ対象オブジェクトの2 つのプロパティの初期値を org プロパティに記憶させる
  $.each([o.$b2,o.$b3],function(i,n){
    n.org={};
    $.each(["posLeft","posTop","width","height"],function(){
      n.org[this] = n[this];
    });
  });

  // 位置やサイズ値をエントリイ内の表内に挿入する関数
  var insertValue = function(ary,obj){ // ary は計測対象要素、obj は挿入対象 table。
    $.each(ary,function(i,n){
      $.each(["left","top","posLeft","posTop","width","innerW","outerW","marginW","height","innerH","outerH","marginH"],
        function(j){
          o.tmp=$(obj).find("input:eq("+(i+j*ary.length)+")");
          o.tmp.before=o.tmp.attr("value") || 0; // 直前の値
          // 各要素の各属性値を取得する。
          o.tmp.attr("value",parseInt(n[this],10)); // n は各要素、this は属性
          o.tmp.after=o.tmp.attr("value");
          // 前後の属性値が異なれば背景色を変化させる。
          o.tmp.before !== o.tmp.after ? o.tmp.css("background","pink")
          : o.tmp.css("background","white");
      });
    });
  };
  insertValue([o.$tArea,o.$t1,o.$t2,o.$t3,o.$t4],o.$tbl1);
  insertValue([o.$b1,o.$b2,o.$b3],o.$tbl2);
  o.$input.css("background","white"); // (初期値として)背景色をホワイトに設定する。
  // 移動アニメーション関数の定義
  var trans = function(){
    o.$input.css("background","white"); // 背景色の初期化
    // box2 の移動アニメーション。trans 関数は call メソッドを使ってo.rndオブジェクト
    // から呼び出すため、this は o.rnd オブジェクトを参照することになる。
    // this の各プロパティは $("#animBtn").click メソッドを参照のこと。
    o.$b2.animate({
      // アニメ終了値は全て相対移動量で設定する
      left: this[1]+"="+this[5][0]+"px",
      top: this[2]+"="+parseInt(this[5][0]/2)+"px",
      width: this[1]+"="+ Math.round(o.$b2.org.width*this[6][0])+"px",
      height: this[2]+"="+ Math.round(o.$b2.org.height*this[6][1])+"px",
      opacity: o.flag ? 0.5 : 1
      // 奇数回目には半透明に、偶数回目には不透明に。
      opacity: o.flag ? 0.5 : 1
    },1000,o.easing[parseInt(o.rnd[0][0]*11)]);
    o.$b3.animate({
      left: this[3]+"="+ this[5][1] +"px",
      top: this[4]+"="+ parseInt(this[5][1]/2) +"px",
      width: this[3]+"="+ Math.round(o.$b3.org.width*this[6][1]) +"px",
      height: this[4]+"="+ Math.round(o.$b3.org.height*this[6][0]) +"px",
      opacity: o.flag ? 0.5 : 1
    },1000,o.easing[parseInt(o.rnd[0][1]*11)],function(){ //アニメーションが終わってから、移動後の座標値を挿入する。
      doCalc(); insertValue([o.$b1,o.$b2,o.$b3],o.$tbl2);
    });
  }
  // アニメの動作ログを作る。
  var insertLog = function(){
    var ary = []; // 偶数回目の移動方向を反転表示とするため
    $.each([this[1],this[2],this[3],this[4]],function(i,n){
      ary[i] = o.flag ? n : (n==="+" ? "-" : "+");
    });
    o.$log.html(
      "<div>box2 の移動方向と距離のための乱数:"+ this[0][0] + "</div>"+
      "<div>box3 の移動方向と距離のための乱数:"+ this[0][1] + "</div>"+
      "<div>box2 の横移動距離:"+ary[0] +"=" + this[5][0]+"px, "+
      "box2 の縦移動距離:"+ary[1] +"=" + parseInt(this[5][0]/2)+"px</div>"+
      "<div>box3 の横移動距離:"+ary[2] +"=" + this[5][1]+"px, "+
      "box3 の縦移動距離:"+ary[3] +"=" + parseInt(this[5][1]/2)+"px</div>"+
      "<div>サイズ増減のための 2 つの乱数:<br />  "+this[6][0]+","+this[6][1]+"</div>"+
      "<div>box2 の横方向サイズ増減倍率、又は box3 の縦方向サイズ増減倍率:<br />  "+this[6][0] +"</div>"+
      "<div>box3 の横方向サイズ増減倍率、又は box2 の方向サイズ増減倍率:<br />  "+this[6][1] +"</div>"+
      "<div>box2 の移動に適用されたeasing:"+o.easing[parseInt(o.rnd[0][0]*11)]+"</div>"+
      "<div>box3 の移動に適用されたeasing:"+o.easing[parseInt(o.rnd[0][1]*11)] +"</div>"
    );
  };
  // animate ボタンがクリックされたら実行する。
  $("#animBtn").click(function(){
    var i=1;
    o.flag = ++o.cnt % 2; // 奇数回目(true) か、偶数回目(false )か?
    if (o.flag){ // 奇数回目ならば
      // 移動方向、移動距離、サイズ変更等に使用する乱数を発生させる。
      // o.rnd は乱数を使った各種プロパティを格納するオブジェクトである。
      o.rnd[0]=[Math.random(),Math.random()];
      // 乱数値の小数点第 1 ~ 4 位迄の各桁の数値が偶数ならば+を、
      // 奇数ならば-を o.rnd[i] に順に代入する。これにより 2 つのボックス
      // それぞれの横及び縦方向の移動方向をランダムに設定する。
      for (;i<5;i++)
          o.rnd[i] = parseInt(o.rnd[0][0] * Math.pow(10,i)) % 2 === 0 ? "+" : "-";
      // box2 と box3 の移動距離を設定 (0 ~ 199)
      o.rnd[5] =[parseInt(o.rnd[0][0]*200),parseInt(o.rnd[0][1]*200)];
      // box2 と box3 のサイズ変動倍率を設定 (-0.5 ~ +0.49999)
      o.rnd[6] =[o.rnd[0][0]-0.5,o.rnd[0][1]-0.5];
    } else {
        // 偶数回目では正負記号を反転させる
        for (; i<5; i++) o.rnd[i] = o.rnd[i]==="+" ? "-" : "+";
    }
    trans.call(o.rnd); // アニメーション開始
    insertLog.call(o.rnd); // アニメーションに係るログを表示する。
    $(this).blur(); // ボタンからフォーカスを外す。
  });

▲ToTop

6. javascript コード作成上留意したこと

HTML、CSS、Javascriptの役割分担

一部に style 属性を設けましたが、出来るだけ文書構造(HTML)、表示(CSS)及び動作(Javascript)は区別しました。

出来るだけ変数の数を減らしました

無名関数で括る必要はなかったのでトップレベルの変数はグローバルになります。そこで可読性をたかめるためにも、トップレベルの変数は 1 つにまとめました。具体的には基礎的な変数を var o オブジェクトプロパティにまとめて、関数以外のトップレベルの変数は var o だけとしました。

jQuery インスタンスの表記上の扱い

jQuery インスタンスを複数回使用する場合には、そのショートカットを作成し、かつ jQuery インスタンスと一目で分かるような独自の名称にしました。具体的には、変数名の 1 文字目に $ 記号を付けました。

ランダムなアニメーションとするために、乱数をさまざまに加工しました

移動する方向、移動距離、サイズの変動率、アニメに適用する easing 関数の選択などを、ランダムに設定しましたが、その各々に乱数を発生させるのではなく、発生させた 1 つの乱数を加工して使い回しました。

アニメ終了後に実行させる行為の扱い

jQuery(要素).animate メソッドでは当該アニメーション終了後に行わせる行為は、所定の方法で記述しなければなりません。或る行為を単にこのメソッド以降の行に既述すると、アニメーション実行中もコード進行は進みますから、アニメーション終了を待たずに当該行為が実行されてしまうからです。

久々に animate メソッドを使用したため、そのことに気がつくのに時間を要してしまい、アニメーション終了後の処置を相応しいタイミングでブラウザに反映させることがなかなか出来ず、徒労を重ねてしまいました。

ボタンクリックによる往復アニメの工夫

アニメーションを起動するボタンは簡潔に 1 つだけとしましたが、ここで作ったアニメーションには往路と復路があり、2 回のアニメーションが 1 セットになっています。そこで最初の段階では、click メソッドではなく toggle メソッドで往復アニメーションを実現させていました。

そして移動方向/距離もサイズ変更も、before と afterで、符号を変えて往復アニメを実現していました。

しかし、効率的なコード記述にすべきだと考え、click 数に応じた flag を用意して click メソッドを採用することにしました。

また、アニメの終点値には、"+=" 等の相対変動値を採用しましたが、これには大きな意味があります。絶対変動値指定を採用すると、コードは長大になるばかりで美しくないのです。相対変動値の採用は往復アニメーションにおける重要なポイントでしょう。

▲ToTop

jquery.js を利用した Ajax 通信の一例(改訂版コードについて)

コード改訂に至る経緯

約 2 年程前に上記関連リストに掲載したエントリイを投稿しました。

そこではjquery.js の Ajax コードを活用する一例として、今閲覧しているエントリイの直前 10 エントリイ、直後 10 エントリイ及び最新の 10 エントリイの、それぞれのタイトルを Ajax 通信で取得してみました。

その後ずっとそれを使い続けてきたのですが、あることからコードを見直す必要に迫られ、今回改めてコードを全面的に見直し、要所をいくつか改訂しました。

改訂を思い立ったのは、直前/最新/直後エントリイタイトルの表示位置が、ブラウザによっては、どうも思ったように描画されていないことに最近気がついたからです。(セットしてから 2 年も経っているのに今更かい?!(^^;; )

IE と Firefox では意図したとおりになっていたのですが、safari や chrome ではずれていたのです。

そしてその原因は、自作コードにおける offsetParent に対する曖昧な対処方法であろう、と推測できました。

しかし、これだけのことでは 2 年前のコードを改訂する動機として弱すぎました。2 年も経つと自作コードであっても可読性はかなり低下するので、思い出す時間と、当時の到達点まで改めて登る努力が必要となります。

重い腰を上げたのは、先月に解読した jQuery(~).offset メソッドや jQuery(~).position メソッドを、今後様々に活用したいと思ったからです。このブログ内で、これらのメソッドや Animation に関するメソッドを活用して、様々な実験を行いたいと思っているのですが、そのためには HTML 要素の位置が正確に把握できなければなりませんし、CSS の決まり事をしっかり押さえなければなりません。

こうして 「offsetParent の扱いを曖昧にしたままでは適切な実験が行えない」 と判断し、曖昧なコードを改善しなければならなくなったのです。

▲ToTop

今回の作業(コード改訂作業を含む)の苦労談(戒めのためのメモ)

1. offsetParent 問題

今年 1 月迄はこのブログの container 要素 には position 指定を行ってきませんでしたが、今回の一連の改訂作業を通じて container 要素には position:relative を指定しました。(※ このブログの container要素は body 要素直下にあり、ブログタイトルや時計、カレンダーそしてエントリイ等を包含する要素 )

その container の中に Javascript コードによって、10項目ずつの以前 / 以後 / 最新エントリイタイトルを表示する 3 つの要素( 以後「popup要素」と呼ぶ )を絶対配置するのですが、container の親要素及び祖先要素に position 指定をしていない状態では、W3C 勧告に従えば、popup 要素の offsetParent は body となります。

しかし、IE( ver 8 の標準モード以外の IE )では、祖先要素に明示的に width や height 値が指定されている要素があると、それが offsetParent となってしまいます。そしてこれまで position 指定をしていなかった container には width が指定されています。この結果 IE 7 以前で閲覧すると、popup 要素が container 内に意図したとおりに表示されていたのです。

他方、safari や chrome では、popup 要素の表示位置が意図した位置から「ずれて」いました。W3C 勧告に従っているこれらのブラウザでは、container 要素内に置かれた popup 要素の offsetParent が body となっていたために、思惑と異なってずれていたのです。(但し、同じように W3C に従っているはずの Firefox では、IE 同様に container 内に収まっていました。その理由を未解明です。)

さて、このようなブラウザによる「ぶれ」を放置したままでは、要素を自在に配置することは出来ません。当然画面内で要素を任意の位置に移動させるアニメーションも思うように操れません。

しかし、offsetParent や offsetTop、offsetLeftプロパティは、ブラウザによって解釈が異なる場合があるだけではなく、そもそも W3C 標準ですらありませんHTML要素の位置取得 - Rails で行こう!)。そのためそのまま使うだけでは、クロスブラウザ対応にはなりません。そこで利用したいと考えているメソッドが、jquery.js の offset と position インスタンスメソッドです。

ところで、W3C 勧告には至っていませんが、offsetParent などについては 「 CSSOM View Module 」という草案が存在しています。そして、jquery.js はそれに準拠するよう配慮されています。

そこで、Ajax コードを改定する前に、まず container 要素に position:relative を指定し、当該要素内に配置される要素の offsetParent が container 要素になるようにしました。これにより CSSOM View Module( W3C Working Draft 04 August 2009) に従って、要素を配置する前提条件が整うことになります。

こうしておいて、jquery.js の offset と position インスタンスメソッドを活用して、 CSSOM View Module に従って、どのブラウザでも同じ位置に要素を配置することを目指します。但し、この問題はエントリイを改めて述べます。

▲ToTop

2. Ajax 通信が終わったことを Javascript インタープリタに知らせる方法

案の定、Ajax 通信コードの改訂作業には時間が掛りました。

久しく Ajax コードを弄っていなかったため、コード内での Ajax 通信独特の「時間差」への配慮も、最初は全く忘れていました。また jqquery.js における Ajax 通信処理の理解が半可通であることも思い知らされました。そこで、自戒を込めて再学習した jquery.js の Ajax 通信コードの要点を整理しておくこととします。

2 年前に完成を見て、つい数日前まで使ってきた従前バージョンの get3modeEntryTitles.js では、setInterval タイマー関数と jQuery.active プロパティ( このプロパティは Ajax 通信の終了を記録する flag の役割を果たしている )を組み合わせて、Ajax 通信の終了を Javascript インタープリタに認識させていました。

しかし、今回改訂した最新の get3modeEntryTitles.js では、ajaxStop イベントハンドラーを使って同じ処理をさせることにしました。この方がコードがすっきりするし、そもそのそのような用途として開発された Ajax イベントハンドラー関数を利用しない手はないからです。

3. 先行する Ajax 通信による取得値を利用する後発 Ajax 通信の起動タイミングの設定

後発 Ajax 通信が先発 Ajax 通信結果を利用する場合、後発 Ajax 通信の開始タイミングがコードの正否を分けます。

勿論、単に後発通信コードを先発通信コードの後に記述するだけでは、何の解決にもなりません。先発通信結果を利用するのですから、それが終わらない限り後発通信を開始してはいけないのに、単にコード記述上前後の順番通りに書くだけでは後発通信は成功しません。

コード進行速度は Ajax 通信速度を遙かに上回るので、先行通信が終わらないうちに後発通信が開始されてしまい、後発通信は必要な情報を先発通信から取得できないからです。

そこで改訂版では、setInterval 関数を起動して、後発通信で使用する先発通信結果の変数が存在するまでループさせ、その存在を確認できてから後発通信を開始するようにしました。

改訂前は jQuery.active 値がゼロになるまでタイマーを繰り返し起動していたのですが、必要な変数の存在確認の方が直接的・具体的であり、可読性も高まります。

▲ToTop

改訂した get3modeEntryTitles.js コード

HTML 文や CSS 文は、jQuery Ajax を活用したエントリイタイトルの各種取得法──集大成! に掲載したものを大部分踏襲しました。但し、CSS 文において visibility 属性の使用はやめ、display属性で統一することにしました。今回 faseIn/Out メソッドによって popup 要素の表示/隠蔽を行うように変えたのですが、これは jquery.js の 各種 animation メソッドは、表示/隠蔽の切り替えに display 属性を利用しているためです。

■改訂した get3modeEntryTitles.js コード
/*
 * fileName : get3modeEntryTitles.js
 * released : 2008/03/01
 * verup : 2008/07/04, 2008/08/08, 2008/08/17, 2010/02/14, 2010/05/15
 */
  1:// 個別エントリイ表示モードでない場合には、何もしないでコード進行を終える。
  2:if (location.href.indexOf("entry")==-1) (function (){return;})();
  3:else{ //個別エントリイ表示の時
  4:(function($){
  5:  var now=function(){return +new Date;};  // 現在時刻取得
  6:  $.tr={ //所要時間計測・待機起動回数記録用 jQuery 拡張オブジェクト
  7:    start:now(),
  8:    registerEvent:"",
  9:    ajax:{ recent:{},before:{},after:{} },
 10:    end:"",
 11:    setEndingCnt:0,waitRecentAjaxCnt:0
 12:  };
 13:  var loading="<div id='loading' class='ta_c'><img src='http://blog-imgs-31.fc2.com/h/k/o/hkom/loading_16.gif' alt='' border='0' width='16' height='16' /> Now Loading...</div>";
 14:  $(function(){
 15:    $("#container").append(
 16:      "<div id='popup_before' class='popup'>" +loading+"</div>",
 17:      "<div id='popup_recent' class='popup'>"  +loading+"</div>",
 18:      "<div id='popup_after' class='popup'>"  +loading+"</div>"
 19:    );
 20:
 21:    // イベント発生要素に沿って popup 要素を配置する(表示へ別メソッドで行う))
 22:    var $popup = $(".popup"),$navi=$(".navi_container"),left,top;
 23:    // 各 popup の縦位置(ナビゲブロックの高さだけ下に)指定
 24:    top = $navi.position().top + (parseInt($navi.css("marginTop")) || 0)+ $navi.outerHeight();
 25:    $popup.width(600);
 26:    var space = $("#container").innerWidth()-$popup.outerWidth();
 27:    // 各 popup の横位置指定
 28:    $.each( ["before","recent","after"] ,function(i,item){
 29:        left= !i ? 0 : i===1 ? space/2: space;
 30:        $('#popup_'+item).css({left:left+"px", top:top+"px"});
 31:    });
 32:
 33:    // 前・最新・後リストの表示/隠蔽イベントハンドラー
 34:    $.each( ["before","recent","after"] ,function(i,item){
 35:      $("#"+ item + "EntryTitle" ).hover(
 36:        function(){  $('#popup_'+item).fadeIn(); },
 37:        function(e){
 38:          var itsIdName = e ? e.relatedTarget.id : window.event.toElement.id;
 39:          if (itsIdName.indexOf("popup") < 0)
 40:            $popup.fadeOut();
 41:        }
 42:      );
 43:    });
 44:
 45:    // 表示された popup の hover イベントハンドラー
 46:    $popup.hover(
 47:      function(e){if (!$(this).is(":hidden")) return;},
 48:      function(e){ $(this).fadeOut(); }
 49:    );
 50:  });  //End of $(function(){})
 51:
 52:  // variable
 53:  var html = {recent:[],before:[],after:[]}, getStr={recent:0,before:0,after:0},
 54:    lastNo, regExpr,thisEntryNo, complement,itval,border={before:0, after:0},realElm = {before:0,after:0},flagStopHandler = false;
 55:
 56:  // 最近のタイトルを取得する関数を定義
 57:  var makeRecentEntryList = function (limit){
 58:    var No, subject, date, iter=0, ret=[], $chd;
 59:    var target ={    //数字は xml ファイル内で登場する順番
 60:          link:[],    //0
 61:          title:[],    //1
 62:      //  description:[],  //2
 63:        //  content:[],    //3
 64:          subject:[],    //4
 65:          date:[]      //5
 66:      };
 67:    $.ajax({
 68:      url: /http:.+fc2\.com\//.exec(location.href)[0] + "?xml" || null,
 69:      type: "GET",
 70:      dataType: "xml",
 71:      global:false,
 72:      success: function(xml){
 73:        var tmpStr = '<div>'+decodeURI(encodeURI("最新のエントリイ情報がありません。"))+'</div>';
 74:        if (xml==undefined || xml==null) {getStr.recent = tmpStr; return;}
 75:        $.tr.ajax.recent["start"]=now();
 76:        $.each(target,function(key){
 77:          $.each($("item",xml), function(i,n){
 78:            $chd = $(n).children();
 79:            ret[i]= [$chd.eq(0), $chd.eq(1), $chd.eq(4),$chd.eq(5)];
 80:            target[key].push( ret[i][iter].text() );
 81:          });
 82:          iter++;
 83:        });
 84:        for (var i=0; i < limit; i++) {
 85:          No = /entry-([0-9]+)/.exec(target.link[i])[1];
 86:          i==0 && (lastNo = Number(No));
 87:          subject =" , " +target.subject[i];
 88:          date =" , " +target.date[i].substring(0,10);
 89:          html.recent.push( "<li><a href='" + target.link[i] + "' target='_blank'>" +  target.title[i] + "</a> (No." + No + subject + date + ")</li>");
 90:        }
 91:        $.tr.ajax.recent["end"]=now();
 92:        getStr.recent =  "<div class='ta_c'><em>Recent " + limit + " Entries</em></div><ul class='ml_1_5'>" + html.recent.join('') + "</ul>";
 93:      }  //End of success()
 94:    });  //End of ajax()
 95:  };  //End of makeRecentEntryTitle func
 96:
 97:  // 前後のタイトルを取得するための準備を行う
 98:  var makeEntryList = function(beforeafter,limit){
 99:    var thisHTTP, getEntryNos=[], thisURL=[];
100:    regExpr = /(http:.+entry-)([0-9]+)/;
101:    thisEntryNo = Number(regExpr.exec(location.href)[2]);
102:    thisHTTP = regExpr.exec(location.href)[1];
103:    border[beforeafter] = Math.min(limit+1, beforeafter == "before" ? thisEntryNo :(Number(lastNo)-thisEntryNo+1) );
104:    if (border[beforeafter]==1) thisURL.length=0;
105:    else {
106:      for (var i=1; i < border[beforeafter]; i++)
107:        thisURL.push(thisHTTP + (thisEntryNo - (beforeafter == "before" ? i : -i)) +".html" );
108:    }
109:    // 準備完了! Ajax 通信開始
110:    getTitlesByAjax.call(this, beforeafter,thisURL);
111:  };
112:
113:  // Ajax 通信により前後のエントリイタイトル等を取得する関数の定義
114:  function getTitlesByAjax(tense,thisURL){
115:    if ( thisURL.length != 0 ) {
116:    $.each(thisURL,function(i,aryitem){
117:      $.tr.ajax[tense][i]={};
118:      $.tr.ajax[tense][i]["start"]=now();
119:      $.get(aryitem,function(data){  //data は thisURL[i] の html テキスト文
120:        regExpr = /<title>(.*)<\/title>/;
121:        var titleStr = regExpr.exec(data)[1].substring(19);
122:        if ( /\S+/.test(titleStr)){  //空白だけのタイトル名は補足しない。
123:          ++realElm[tense];
124:          html[tense][i] = "<li><a href='" + aryitem + "' target='_blank'>" + titleStr +" (EntryNo." + /entry-([0-9]+)/.exec(aryitem)[1] + ")</a></li>";
125:        }
126:        $.tr.ajax[tense][i]["end"]=now();
127:      },"text");
128:    });
129:    }
130:  };
131:
132:  function ajaxStopHandler(){
133:    flagStopHandler = true;
134:    $.each(["before","after"],function(i,tense){
135:      if (realElm[tense]==0)
136:        getStr[tense]="<div class='ta_c'>"+ (tense=='before' ? "Before " : "After ") + "Entry "+ decodeURI(encodeURI('はありません。')) + "</div>";
137:      else {
138:        complement = (border[tense]-1 -realElm[tense]!==0) ? 
139:          " ( " +decodeURI(encodeURI('欠番があります。')) +" )" : "";
140:        getStr[tense] = "<div class='ta_c'><strong>"+ (tense==='before' ? "Before " : "After ") + realElm[tense] + " Entries" + complement +"</strong></div><ul style='margin-left:1.5em;list-style-type:disc'>" + html[tense].join('') + "</ul>";
141:      }
142:    });
143:    var beforeTime= realElm.before!==0 ? "<li>以前タイトル取得 Ajax 通信所要時間: "+ ($.tr.ajax.before[realElm.before-1].end-$.tr.ajax.before[0].start)/1000 +" 秒</li>" : "";
144:    var afterTime= realElm.after!==0 ? "<li>以後タイトル取得 Ajax 通信所要時間: "+ ($.tr.ajax.after[realElm.after-1].end-$.tr.ajax.after[0].start)/1000 +" 秒</li>" : "";
145:    var AjaxLog ="<ul style='margin:0.5em;border-top:white dotted 1px;padding:0.5em 0.5em 0 1em;list-style-type:circle'>"+
146:      "<li>クリック後 Ajax 通信開始迄の所要時間: "+($.tr.ajax.recent.start-$.tr.start)/1000 +" 秒</li>"+
147:      "<li>最新タイトル取得 Ajax 通信所要時間: "+ ($.tr.ajax.recent.end-$.tr.ajax.recent.start)/1000 +" 秒</li>"+ beforeTime + afterTime +
148:      "<li>このプロジェクト全体の所要時間: "+ (($.tr.end=now())-$.tr.start)/1000 +" 秒</li></ul>"+
149:      "<div style='text-align:right;margin:-2em 1em 0.5em 0'><button style='display:block;margin:-1em 0 0 auto;width:16em' title='このボタンをクリックするとここで行った Ajax 通信に関するこのブログのエントリイが開きます。' onclick='this.blur();window.open(\"http://hkom.blog1.fc2.com/blog-entry-626.html\",target=\"_blank\")'>この Ajax 通信や所要時間について</button></div>";
150:    $(function(){
151:      $("#popup_before").html(getStr.before + AjaxLog);
152:      $("#popup_after").html(getStr.after + AjaxLog);
153:      $("#popup_recent").html(getStr.recent + AjaxLog);
154:    });
155:  }
156:
157:  $(document).ajaxStop(function(){
158:    ajaxStopHandler();
159:  });
160:  // 前後及び最新のタイトルリスト作成
161:  makeRecentEntryList(10);  //最近リスト取得実行
162:  makeEntryList("before",10);  // 以前エントリイ
163:  function nextAjaxTimer(){
164:    $.tr.waitRecentAjaxCnt++;
165:    // makeRecentEntryList により lastNo 値を取得するまでは起動しない。
166:    if (!!lastNo) {
167:      if (ival) {clearInterval(ival);ival=null;}
168:      makeEntryList("after",10); // 以後エントリイ
169:    }
170:  };
171:  var ival = setInterval(nextAjaxTimer,100);
172:})(jQuery);
173:}

jquery.js のアニメーションコードの活用 ( 4 ) slideToggleEx プラグインの簡便な活用のために(完全版)

slideToggleEx プラグインは不完全だった

エントリイNo.731 において、slideToggleEx 自作プラグインメソッドについて詳述しました。

ところが、そこで紹介したプラグインメソッドコードは不完全でした。隠蔽状態から起動するアニメーション対応を全く考慮していなかったので、その状態で起動すると思わぬ動きをしてしまうのです。つまり、実は半分しか完成していないのでした。(^^;; ヒヤアセ

そのことに気がついたのは 9 月中旬。その時点から、改めてコード全体を見直さざるを得なくなり、改めて animate メソッドの再学習を迫られたのです。

その後一ヶ月余り。四苦八苦、七転び八起きを繰り返した末に、昨晩やっと隠蔽状態からの起動にも対応した slideToggleEx 版が完成したので、ここに投稿することにします。

用意するもの・ことは以下の 5 つです。(この部分は No.731 の再掲)
  1. include 用 slideToggleEx プラグインファイルと include タグ要素
  2. slideToggleEx が適用されるタグ要素
  3. 起終点を右側にする場合には、対象タグ要素に width スタイル属性を設定します。また起終点を下にする場合には height スタイル属性を設定します。そうしないと animate メソッドの中で適正な幅や高さが計測されない場合があるためです。
  4. slideToggleEx を呼び出し、適用させるタグ要素(一般的には button タグでしょう)
  5. その button タグのクラス属性に fireSlideToggleEx-n を追加します。( n は 0 から 8 の整数)

n は起終点を意味し次の通りの意味を持ちます。

0:ボックスの中心、1:左上端、2:右上端、3:右下端、4:左下端、
5:上辺、6:右辺、7:下辺、8:左辺

これだけ設定すれば、トリガーである button をクリックすると、n で指定した位置を起終点として、トリガーボタンの直後に配置された要素が slideToggleEx 適用対象要素となり、隠蔽されたり表示されるようなコード( エントリイ末尾に掲載 )を書きました。

「 これだけ 」と言っても結構たくさんあるため、最初は面倒に感じますが、直ぐに慣れますし、実際に使ってみると決して大変な手間ではありません。

必要な HTML 構造(この部分も No.731 の再掲)

上の 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文構成にしておけば、運用に当たってどのようにしたかを思い出すのも容易ですし、仮に構造を忘れても直感的に思い出すことが可能となります。

▲ToTop

以上の前提で作った slideToggleEx プラグイン活用コード

アニメ継続時間と easing 関数は、後述する 「 click イベントを登録し slideToggleEx メソッド適用対象をピックアップするコード 」 において、1 秒に設定し、easing は無指定としています。このコードを弄れば自在に継続時間と easing 関数は変更できますが、使う際にその都度 duration と easing 関数を変更する必要はないと考え、(1) トリガーとなるボタンタグと (2) slideToggleEx 対象のブロックタグ、及び (3) slideToggleEx-n クラス名を指定するだけで使えればよいと判断しました。

以下に掲載したコードリストの表示と隠蔽には、当然 slideToggleEx を適用しています。直下のボタンをクリックすると、 slideToggleEx メソッドが起動し、アニメ対象要素であるコードリストが当該要素の右上から展開し、再びボタンをクリックすると右上へ隠蔽されます。( 起終点= 2 )

コードには各行に詳細な説明を記述したので、何を行っているのか分かると思います。

■プラグイン slideToggleEx() メソッドコード(完全版)
  0:// jQuery plugin : slideToggleEx() Release at 2009.8.21, ver 1.1:9.9, 1.3TheLastVer:10.9 01:00am
  1:(function($){
  // アニメーションコードの性格を簡単に記述しておく
  2:var ver="relative animation under position relative";
  // プラグイン指定
  3:$.fn.slideToggleEx = function( type, duration, easing, fn ){
  4: if (type > 8) // エラー対処
  5:  (function(){
  6:   alert("第 1 引数は 0~8 だけが指定できます。\nやり直してください。
      \n"+"slideToggleEx( 0~8, 継続時間, easing関数, アニメ後実行関数 )"); return;
  7:  })();
   // 変数設定、初期値複写
  8: var $target = this, o = $.extend({},$.fn.slideToggleEx.opts);
   // 9 ~ 23 行は、o オブジェクトにtargetとプロパティがない場合、
    * またはターゲットが異なった場合、つまり初めてそのインスタンスから呼ばれたか、
    * インスタンスが変わった場合に起動される。
    * このブロックはアニメ対象に対して様々な初期化を行う。
    */
  9: if(!o.target || o.target[0]!==$target[0]) {
 10:  $.extend(o,{
 11:   orig : o, // 当面使わないが念のために o をバックアップ
 12:   target : $target,
 13:   el : $target[0], // アニメ対象ノードへの参照を取得
 14:   type : type,
 15:   initstate : !$target.is(":hidden"), // アニメ前の状態が隠蔽か表示か
 16:   cnt : 1 // インスタンス毎の起動回数カウンター
 17:  });
    // アニメ対象が切り替わり、再びあるインスタンスに slideToggleEx メソッドが
    // 適用された時には、最初に呼び出された時の設定を再利用できるように data
    // メソッドを活用してコード進行の効率化を図る。
    // また o オブジェクトにアニメ対象要素等のサイズに係るプロパティを追加する。
 18:  $.extend(o, $.data(o.el,"initdone") || getSize());
    // アニメ対象ノードの固有 style 値を取得する。
 19:  for (var p in o.cssdefault.H) o.cssOrig.H[p] = $.css(o.el,p,true);
 20:  for (var p in o.cssdefault.W) o.cssOrig.W[p] = $.css(o.el,p,true);
    // 上のメソッドでは単位が付かないので付与する。
 21:  o.cssOrig.H.height +="px"; o.cssOrig.W.width +="px";
      // o オブジェクトを $.fn.slideToggleEx.opts オブジェクトに併合する。
 22:  $.extend($.fn.slideToggleEx.opts,o);
 23: }
   /* 24 ~ 36 行迄のブロックは、slideToggleEx が呼び出される度に
    * 作用する。27 行で type 値をチェックするのは、同一要素における異なる type 値
    * によるslideToggleEx メソッドの連続起動の場合に対処するためである。
    * slideToggleEx メソッドの連続起動の場合には、呼び出される度に o.duration、
    * o.easing、o.complete が変化する可能性があるので、これらも呼び出し毎に
    * 定義する必要がある。
    */
   // window サイズが変更された時には親要素サイズを計測し直す。
 24: window.onresize = getParentWH;
 25: o.cnt = $.fn.slideToggleEx.opts.cnt++; // カウンター値を 1 増する。
 26: o.odd = o.cnt % 2; // o.cnt が奇数か偶数かを判定する
 27: o.type = o.type!==type ? type : o.type;
   // duration などの初期値は $.fn.slideToggleEx.opts に用意しておく。
 28: o.duration = duration || o.duration;
 29: o.easing = typeof duration !=="object" ? (easing || o.easing) : "";
 30: o.complete = typeof duration !=="object" ? ($.isFunction(fn) && fn || o.complete) : "";
 31:
   /* アニメ対象ノードに AdjustN( N は type 値 )名で
    * 関連づけられたオブジェクトがなければ、adjustcssAnim 関数を呼び出し、
    * その返値を o.cssAdjust.show 及び .hide プロパティに代入する。
    * 関連づけられたオブジェクトがあればそのまま利用する。
    * ここに o.cssAdjust は animate メソッドで利用するアニメ用 CSS プロパティの内、
    * 起終点位置に応じて変化する top 及び lef 値だけを保持するオブジェクトである。
    * show 及び hide プロパティには "+=" などの relative アニメーション値が設定される。
    */
 32: o.cssAdjust = $.data(o.el,"Adjust"+o.type) ||
 33:  $.data(o.el,"Adjust" + o.type, {show:adjustcssAnim(true), hide:adjustcssAnim(false)});
   /* アニメ対象ノードに AnimN( N は type 値 )名で関連づけられた
    * オブジェクトがなければ、setAnimCSS 関数を呼び出してその返値を o.cssAnim
    * オブジェクトに代入する。関連づけられたオブジェクトがあればそのまま利用する。
    * ここに cssAnim は animate メソッドで利用するアニメ対象プロパティの全てを
    * 保持するオブジェクトであり、29 行で取得した top値と left値も併合する。
    */
 34: o.cssAnim = $.data(o.el,"Anim" + o.type) || $.data(o.el,"Anim" + o.type, setcssAnim());
   /* アニメ対象ノードに BeforeN( N は type 値 )名で関連づけられた
    * オブジェクトがなければ、makecssBefore 関数を呼び出してその返値を o.cssBefore
    * オブジェクトに代入する。関連づけられたオブジェクトがあればそのまま利用する。
    * ここに cssBefore は animate メソッド起動直前のアニメ対象要素のスタイル属性値を
    * 取得し、あるいは設定するためのオブジェクトである。
    */
 35: o.cssBefore = $.data(o.el,"Before" + o.type) || $.data(o.el,"Before" + o.type, makecssBefore());
   // 当該のアニメ対象要素に初めて、あるいは別のアニメ対象操作後に再び
   // slideToggleEx メソッドを適用した時に、最初に起動された場合にのみ適用するコード。
   // 対象要素の表示状態に応じて、animate メソッド適用前の CSS 値を設定する。
 36: if (o.cnt==1) $target.css( o.initstate ? o.cssBefore.show : o.cssBefore.hide );
 37:
   /* アニメーション起動
    * 第 2 引数がオブジェクトではない時
    * 要素の animate メソッド適用前の表示状態と slideToggleEx メソッドの起動
    * 回数に応じて、当該メソッド第一引数である CSS オブジェクトを選択的に適用する。
    */
 38: if (typeof o.duration !=="object")
 39:  return this.animate( (o.initstate && !o.odd || !o.initstate && o.odd)
 40:   ? o.cssAnim.show : o.cssAnim.hide, o.duration, o.easing, o.complete );
   // 第 2 引数がオブジェクトの時
   // 要素の animate メソッド適用前の表示状態と slideToggleEx メソッドの起動
   // 回数に応じて、当該メソッド第一引数である CSS オブジェクトを選択的に適用する。
 41: else
 42:  return this.animate( (o.initstate && !o.odd || !o.initstate && o.odd)
 43:   ? o.cssAnim.show : o.cssAnim.hide, o.duration ); // アニメ起動
 44:// 以下は以上迄のコードから呼び出される関数群である。
   /* getSize はアニメ対象ノードの position 指定を行い、サイズを測る関数。
    * この関数は或る要素に対して slideToggleEx メソッドを適用した初回だけにたった1 回
    * だけ起動される。 54 行と 35 行の data メソッドによって data 関連づけを行わっている
    * ので、同じ要素に対する 2 度目以降の呼び出し時には、たとえ非連続的に呼び出された
    * としても、この関数は呼び出されない。
    */
 45: function getSize(){
    // position 指定を relative に
 46:  if ($target.css("position") ==="static") $target.css({position:"relative"});
 47:  var obj={}, parent={};
 48:  return obj= {
       // 親要素の幅と高さを取得
 49:   parent : {W:getParentWH().W, H:getParentWH().H},
       // アニメ対象要素のマージン辺までの高さ(算出スタイル値)取得
 50:   oH : parseInt($.css(o.el,"height",true,"margin")),
       // アニメ対象要素のマージン辺までの幅(算出スタイル値)取得
 51:   oW : parseInt($.css(o.el,"width",true,"margin")),
       // アニメ対象要素のボーダー辺までの高さ(算出スタイル値)取得
 52:   oHb : parseInt($.css(o.el,"height",true,"border")),
       // アニメ対象要素のボーダー辺までの幅(算出スタイル値)取得
 53:   oWb : parseInt($.css(o.el,"width",true,"border"))
 54:  };
 55: }
 56: function getParentWH(){ // アニメ対象の親要素のサイズを計測取得する関数
 57:  var parent = $target.parent();
 58:  return o.parent={W:parent().width(),H:parent().height()};
 59: }
   // アニメーション起終点に応じたアニメ用 top/left 値設定関数
 60: function adjustcssAnim(showhide){
 61:  var plmn = showhide ? "-=" : "+=";
 62:  return o.type==0 ? {top:plmn + o.oH/2 +"px", left:plmn + o.oW/2 +"px"} : // center
 63:   o.type==1 ? {} : // 左上端。top も left もアニメ対象プロパティとしない。
 64:   o.type==2 ? {left:plmn + o.oW +"px"} : // 右上端
 65:   o.type==3 ? {top:plmn + o.oH +"px", left:plmn + o.oW +"px"} : // 右下端
 66:   o.type==4 ? {top:plmn + o.oH +"px"} : 3aspan class="aquamarine"
 
 67:   o.type==5 ? {} : // 上辺。top も left もアニメ対象プロパティとしない。
 68:   o.type==6 ? {left:plmn + o.oW +"px"} : // 右辺
 69:   o.type==7 ? {top:plmn + o.oH +"px"} : // 下辺
 70:   {} ; // 左辺。top も left もアニメ対象プロパティとしない。
 71: }
   /* アニメ用プロパティを作成する関数
    * o.cssdefault を基にして横方向/縦方向のプロパティを作成し、またその有無を
    * type 値毎に変化させる。
    * 例えば、type 値が 5 の場合、横方向のプロパティはアニメ対象とはしない。
    * また、margin は auto が指定されている場合にはアニメ用プロパティとせず、
    * その他の場合には他のプロパティと同様に"toggle"とする。
    * 79 行で横/縦方向のプロパティを合体させ、不透明度もアニメ用プロパティと
    * して追加する。
    * 最後に 80~82 行において、それまでに作ったアニメ用 CSS オブジェクトに
    * アニメーション起終点に応じて変化する top と left プロパティを追加する。
    */
 72: function setcssAnim(){
 73:  var obj = {};
 74:  obj.W = (o.type!=5 && o.type!=7) ? $.extend({}, o.cssdefault.W,
 75:    (o.oW!==o.oWb) ? {marginLeft:"toggle",marginRight:"toggle"} : {}) : {};
 76:  obj.H = (o.type!=6 && o.type!=8) ? $.extend({}, o.cssdefault.H,
 77:    (o.oH!==o.oHb) ? {marginTop:"toggle",marginBottom:"toggle"} : {}) : {};
 78:  obj = $.extend({}, obj.W, obj.H, {opacity :"toggle"});
 79:  return o.cssAnim = {
 80:   show:$.extend({},obj,o.cssAdjust.show),hide:$.extend({},obj,o.cssAdjust.hide)
 81:  };
 82: }
   // animate メソッド適用前の適用対象要素の CSS 値を作成する関数
 83: function makecssBefore(){
 84:  var obj = {}, ret = {};
    /* margin をアニメ対象にした場合において、margin 値が "auto" の時には、
     * jquery.js の各種メソッドでサイズを測定しても margin 値が捕捉できない。
     * その結果アニメーションは予期せぬ動きをしてしまうため、まず、margin 値を
     * 強制的にゼロにし、替わりに auto 指定によって確保された margin 値に相当する
     * left 値と top 値に置き換える。これにより予期したとおりのアニメーションを
     * 引き起こさせる。
     */
 85:  // if marginLeft/Top == "auto", fix marginLeft/Top.
 86:  obj.W = (o.el.style && $.attr(o.el.style,"margin-left")==="auto") ?
 87:   {left : (o.parent.W - o.oWb)/2 +"px", marginLeft : "0px", marginRight : "0px"} :
     /* この場合も、margin 値は $.css メソッドを使って取得しないと駄目。
      * 例えば margin 指定が簡略的に margin: 10px のように行われている場合には、
      * jquery.js は margin-left、margin-right などの個々の margin 値を取得しない。
      */
 88:   {left :"0px",
 89:    marginLeft:$.css(o.el,"margin-left",true), marginRight:$.css(o.el,"margin-right",true)};
 90:  obj.H = (o.el.style && $.attr(o.el.style,"margin-top")==="auto") ?
 91:   {top : (o.parent.H - o.oHb)/2 +"px",marginTop : "0px", marginBottom : "0px"}:
 92:   {top : "0px",
 93:    marginTop:$.css(o.el,"margin-top",true), marginBottom:$.css(o.el,"margin-bottom",true)};
    // ここ迄の処理結果を show プロパティに格納する。
    // このプロパティがアニメ対象が表示されている時のアニメ前 CSS 値となる。
 94:  ret.show = $.extend({}, o.cssOrig.H, o.cssOrig.W, obj.W, obj.H);
    /* 以下はアニメ対象が隠蔽されている場合の処理
     * アニメ用CSSオブジェクトの hide オブジェクトに top プロパティが存在している
     * 場合にのみ適用する。
     * top プロパティが存在していない場合には、top プロパティはアニメ対象としない。
     */
 95:  if (o.cssAdjust.hide.top) // アニメ用 CSS オブジェクト作成関数を利用して
     // アニメ前 CSS オブジェクトを作成する。
 96:   obj.top = new Number(parseInt(obj.H.top))
 97:    + new Number(parseInt(o.cssAdjust.hide.top.slice(2)))+"px";
    // アニメ用CSSオブジェクトの hide オブジェクトに left プロパティ
    // が存在している場合にのみ適用する。
    // left プロパティが存在していない場合には、left プロパティはアニメ対象としない。
 98:  if (o.cssAdjust.hide.left) // アニメ用 CSS オブジェクト作成関数を利用
     // してアニメ前 CSS オブジェクトを作成する。
 99:   obj.left = new Number(parseInt(obj.W.left))
100:    + new Number(parseInt(o.cssAdjust.hide.left.slice(2)))+"px";
    // ここ迄の処理結果を hide プロパティに格納する。
    // このプロパティがアニメ対象が隠蔽されている時のアニメ前 CSS オブジェクトとなる。
101:  ret.hide = $.extend({}, ret.show, obj);
    // data メソッドによる関連づけを行ってから ret オブジェクトを返す。
102:  return ret;
103: }
104:} // End of $.fn.slideToggleEx
105:
106:// default 値設定と記憶用プロパティとしての機能のために
107:$.fn.slideToggleEx.opts = {
   // 既定値としての引数設定
108: duration : 1000, easing : null, complete : function(){},
   /* auto 指定への対応が必要な margin、及び type 値に応じて変化させる
    * 必要のある top と left ───これらを除くアニメ対象プロパティリスト。
    * 羅列されているプロパティは、最小値の 0 又は 1 (width と height のみ)と
    * 要素固有のプロパティ値との間でアニメーションさせることになる。
    * 高さ方向と横方向とを区別するのは、type 値に応じてアニメ対象プロ
    * パティの要/不要が変化するためである。
    */
109: cssdefault : {
110:  H : {
111:   height:"toggle",paddingTop:"toggle",paddingBottom:"toggle",
112:   borderTopWidth:"toggle",borderBottomWidth:"toggle"
113:  },
114:  W : {
115:   width:"toggle", paddingLeft:"toggle",paddingRight:"toggle",
116:   borderLeftWidth:"toggle",borderRightWidth:"toggle"
117:  }
118: },
119: cssAdjust : {},
120: cssOrig : {H:{},W:{}},
121: cssBefore : {}
122:};
123:})(jQuery);

▲ToTop

slideToggleEx Javascript コード解説

直上に掲載したコードに至るまで、実は多くのバージョンを作成し、失敗しては作り直す「らせん的」過程を何度も繰り返しました。しかし、その苦労話の前に、まずコード作成の基本的方針を述べておくべきでしょう。

slideToggleEx が想定しているシーン
  1. slideToggleEx アニメ対象ノードは表示されていても隠蔽されていても、いずれの状態でも良い。
  2. 1 つの homepage や blog エントリイ内に、複数の slideToggleEx アニメ対象ノードが存在し、それぞれのアニメ起終点は、各々同一でも良いし、異なっても良い。
  3. 1 つのアニメ対象ノードに対して連続して slideToggleEx メソッドを起動することもあり、その場合にはアニメ起終点がその都度変わることもあり得る。
  4. 独自仕様の多い IE においても起動出来るアニメーションとする。
slideToggleEx コードの効率化
  1. slideToggleEx メソッドを同一ノードに適用する場合には、コード進行過程を出来る限り繰り返さないようにする。そのために jQuery.data クラスメソッドを活用してアニメ対象ノードに情報を関連づける。
  2. 一度計算した値や使用した関数は、結果を出来る限り再利用する。そのために、jQery プロトタイプオブジェクトのプロパティを活用する。

▲ToTop

コード作成上の苦労話・強調したい点

1. メソッド終了後にも情報を保持させる措置

まず苦労したのは、slideToggleEx プラグインの再起動の際に、従前値を保持させる措置でした。

無名関数のトップレベルで、slideToggleEx メソッド外に変数を用意すれば解決することなのですが、是が非でもプラグインのメソッド外に変数を置かないでまとめたかったので、拘ってコードを作りました。その結果、メソッド外となる点では変わらないのですが、『Javascript 第 5 版』p.144 や cycle プラグインを参考にして slideToggleEx メソッドのプロパティに値を保持させることにしました。

メソッド実行が終わるとその中で定義された変数は消失してしまうので、当該メソッドの起動回数を記憶させる変数は当該メソッド外になければなりません。しかし外に変数を置くのは美しくありません。

そこでメソッドのプロパティを記憶装置にすることにしました。コードの 107 行から 122 行がその記憶装置です。

2. アニメーション開始前の対象ノードの CSS 値の処理

要素が隠蔽されていても表示されていても、要素固有の margin、border、padding、width、height 値などは保持されています。隠蔽されていてもそのスタイル諸値がゼロになるわけではありません。そのことをまず把握し理解する必要があります。( 実は私はこのことへの理解が不十分でした。 )

次に、slideToggleEx メソッドの適用対象が複数存在しているエントリイにおいて、アニメ起動が異なる要素に対してアト・ランダムに行われる場合、それぞれの要素に対して適切なアニメを起こさせることがかなり難しいことでした。

以前、slideToggleEx メソッド対象となった要素は偶々隠蔽されているかもしれないし、表示されているかもしれません。今 slideToggleEx アニメを引き起こした要素も表示されているかもしれないし、隠れているかもしれません。このようなアト・ランダムな状態においても、それぞれの要素毎に適切な slideToggleEx アニメーションが引き起こされるようにしなければなりません。

以上を踏まえた処理として cssBefore オブジェクトを用意しました(32 ~ 33 行)。このオブジェクトによりアニメーション起動前の要素のプロパティを事前に設定するわけです。これにより隠蔽状態から表示へとアニメートする要素が、適切な位置で表示されるようになりました。

3. margin 値の扱い

margin 値は一般に 「 margin:1em auto 」 などのように簡略的に指定されている場合が多いでしょう。

ところがこのような指定の場合には、jquery.js の css メソッド等を活用しても margin-left 等の個々の margin 値を取得することが出来ません。特に auto 指定がされている場合、margin をアニメプロパティに指定すると全く予想外のアニメーションが引き起こされてしまいます。jquery.js は auto 指定された margin 値を数値的に捉えることが出来ないので、margin-left、margin-right 等のプロパティをゼロと判読してしまうためです。

こうして、margin 値が様々な様式で指定されている場合でも、それを適切な数値として把握する必要が生じます。

そのための処理は makecssBefore 関数で行わせました。

▲ToTop

4. アニメ開始前における対象要素の CSS 値設定

animate メソッドは、アニメ開始前の要素の様々な属性値(スタイル属性値を含む。width、margin etc.)をアニメ開始値として採用します。(もし或るプロパティの属性値がない場合には開始値を 0 とします。)このため表示されている要素にアニメーションを適用する場合には、一般的にはアニメ前 CSS 値を設定する必要はありません。

例えばアニメ対象要素に width="380px" が設定されていれば、ブラウザがその値を解釈して、380px 幅の要素を表示しているわけです。この場合 width プロパティのアニメ開始値が何も指定されていなければ、 animate メソッドは要素固有値である 380px を width プロパティのアニメ開始値とします。

ところが隠蔽されている要素にアニメーションを適用する場合には様相が異なってきます。

隠蔽されている要素を表示するアニメーションの場合でも、その隠蔽が一般的にそうであるように display = "none" 指定によって行われている場合には、対象要素の固有の属性値は存在し続けます。要素が単に非表示になっているだけで、display = "none" 以外の属性値は表示されていた時の値が保持され続けます。

この状態に対して隠蔽要素を表示させる animate メソッドを適用すると、animate メソッドの仕様上、要素固有値がアニメ開始値になるのですから、アニメプロパティが要素固有値となってしまいます。この結果、0 からスタートさせたかったアニメーションは引き起こされません。

従って、隠蔽要素を表示させるアニメーションの場合、アニメ開始値を animate メソッド起動前に強制的に指定する必要があります。こうして強制的に CSS 値を指定するための cssBefore オブジェクトが必要・不可欠となります。

さて、その cssBefore はアニメーションの初期値を定めるわけですから、アニメーションの態様に応じて( つまり起終点位置に応じて )変化します。ボックス要素の左上、上辺、左辺などを起終点とするアニメーションの場合には問題になりませんが、例えば、アニメが要素の中心位置から始まる場合には、top と left の開始値をボックス内容辺、ボーダー辺あるいは margin 辺までの、幅や高さの半分の値に設定する必要があります。また、開始点がボックスの右上の場合には、left の開始値をボックス内容辺、ボーダー辺あるいは margin 辺までの幅に設定する必要があります。

以上に述べた隠蔽状態における cssBefore オブジェクトの作成を makecssBefore 関数の後半で行っています。

なお、アニメプロパティ値を toggle や show に指定すると、animate メソッドは要素の固有属性値をアニメの終了値として利用します。この利便性故に、アニメプロパティ値に toggle を多用することになります。

▲ToTop

5. $.data メソッドの活用

slideToggleEx プラグインでは以下の 4 箇所で $.data メソッドを利用しました。

  1. 18 行 …… $.extend(o, $.data(o.el,"initdone") || getSize());
  2. 32~33 行 ……o.cssAdjust = $.data(o.el,"Adjust"+o.type) ||    $.data(o.el,"Adjust" + o.type, $.data(o.el,"Adjust" + o.type, adjustcssAnim(false));
  3. 34 行 …… o.cssAnim = $.data(o.el,"Anim" + o.type, $.data(o.el,"Anim" + o.type, setcssAnim());
  4. 35 行 …… o.cssBefore = $.data(o.el,"Before" + o.type) || $.data(o.el,"Before" + o.type, makecssBefore());

このメソッド利用した目的は、ずばりコード進行の効率化/高速化です。このメソッドを使うことにより、不必要に同じ計算を繰り返させたくなかったのです。

上の 4 例は皆同様な形式を取っているので、どれか 1 つ分かれば他は推して知るべしです。なので、ここでは一番最後の 35 行で使った $.data メソッドについて触れておきます。

$.data(o.el,"Before" + o.type) は、アニメ対象要素 o.el に "BeforeN" ( N は type 値 )と言う名称で関連づけられたオブジェクトがある場合には、そのオブジェクト( cssBefore オブジェクト)を返し、それが左辺の o.cssBefore に代入されます。一方、関連づけられた情報がなければ false を返すので、次にある || 演算子が働きます。

|| 演算子が作用すると、オペランドである makecssBefore 関数が実行されます。

この関数では最後の行に $.data メソッドがあるので、関数によって算出したオブジェクトを、このメソッドによりアニメ対象要素 o.el に "BeforeN" 名で関連づけます。関連づけられたオブジェクトは return により左辺の o.cssBefore オブジェクトに返されます。

こうして、同一要素に対する同一 type の slideToggleEx メソッド起動時には───実際、表示/隠蔽はセットで履行されるはずだから、多くの場合 slideToggleEx メソッドは、同一要素に対して同一 type 値で繰り返し実行されます。───最初の呼び出し時にだけ makecssBefore 関数が実行され、二度目以降は固有の名称で要素に関連づけられているオブジェクト値が利用されて、 makecssBefore 関数は実行されません。これがコード進行の「最初の」「効率化」であり「高速化」です。

$.data メソッドを利用したことによる効率化は、これだけではありません。或る要素Aが slideToggleEx によって表示/隠蔽され、次に別の要素Bがやはり slideToggleEx で表示/隠蔽されたと仮定します。この後に再び要素Aに slideToggleEx メソッドを適用した場合に、更なる効率化・高速化が発揮されます。

既に $.data メソッドによるA、B各々の要素へのオブジェクトの関連づけは済んでいますから、要素Bに続けてAに slideToggleEx アニメを適用した時には、既にAに関連づけられているオブジェクトが利用され、makecssBefore 関数は起動されません。こうして無駄なコード進行が排除されます。

以上のことが 4 つの関数に対して行われるので、現在操作対象となっている頁がリロードされない限り、slideToggleEx アニメーションが一度適用された要素においては、 slideToggleEx 内にある様々な関数を殆ど実行する必要がなくなります。こうしてコード進行が大いに高速化されます。高速化は言い換えれば省エネでもあるので、流行の言葉を使えば「エコ」なコード進行が行われると言っても良いかもしれません。

▲ToTop

クリックイベント登録と slideToggleEx メソッド適用対象の簡便な登録コード

さて、slideToggleEx を使うシーンは、ボタンをクリックして或るコンテンツを表示したり隠蔽する場合が多いと思われます。そうだとすれば、その一連の操作( クリックイベントの登録・或るコンテンツの指定・slideToggleEx メソッドの要素への適用 )をコード化して半自動化してしまえば、大変楽になります。

次に述べるコードは、この一連の操作、つまりボタンへのクリックイベント登録と、クリック時の slideToggleEx メソッド適用対象の特定とそのメソッドの起動をまとめて行うものです。

具体的には 「 或るボタンがクリックされたら、slideToggleEx アニメーションが適用される或る要素が特定され、slideToggleEx メソッドが起動する 」 そのような Javascript コードです。

そのコードでは、同一ページ内に存在する、複数の同一又は異なる type の slideToggleEx アニメーションの、トリガーボタンとアニメ対象を一括して登録します。slideToggleEx の引数であるアニメ継続時間、easing 関数及びアニメ終了後の実行関数は、コードの冒頭で登録し、これを slideToggleEx メソッドに渡すようにしています。

そもそもコード作成当初では、slideToggleEx メソッドの引数を何とか HTML 文内で行いたいと考えました。クラス指定によって、アニメの起終点 ( type ) を指定するだけではなくその他の引数もまとめて指定したいと思いました。

しかし、クラス名文字列の中に、duration、easing などの文字をその都度組み込むと、冗長なクラス名指定となってしまい、いかにも面倒ですし、値だけを羅列するようにしても煩雑で順番を含めてわかりにくくなります。それにボタンクリックによる要素の表示/隠蔽操作において、その都度 duration や easing の値を変化させる必要性が高いとは思えません。

以上から、クラス名の中に slideToggleEx メソッドの引数を盛り込むことはやめました。

なお、ここにおける duration 等の指定は、ここで対象としている「 ボタンクリックによって起動される slideToggleEx 起動方法 」 において、slideToggleEx メソッドの引数を指定するものです。他方、slideToggleEx プラグインコードにおける $.fn.slideToggleEx.opts オブジェクト内でも duration 等を指定していますが、これは slideToggleEx メソッドの一般的な既定値を設定するものです。

下のボタンをクリックすると隠蔽状態にあるそのコードが、右下から展開され、再びボタンをクリックすると右下に隠れます。(type 3)

▼このコードは、メソッドプラグインではありませんが、プラグインとセットで include
 ファイルに登録しておくと重宝します。これがないと fireSlideToggleEx-n のクラス指定を
 しても next sibling 要素などを slideToggleEx の適用対象とすることは出来ません。

// button 要素へのクリックイベント登録及び slideToggleEx メソッド適用対象のピックアップ
// クリックイベント起動ボタンと slideToggleEx 適用対象を登録する。
124:// regist click Event to fire slideToggleEx() for nextBlock
125:$(function(){ // 当然 DOM が Ready されてから起動する必要があります。
   // 初期値設定
126: var duration = 1000, easing = null, complete = function(){}, clk = {};
127: function func(j){
128:  $.each(clk[j],function(i){
     // target オブジェクトは slideToggleEx 対象の個々のノード 1 つだけを格納する。
129:   var $target={};
     // $target オブジェクトの j プロパティに slideToggleEx 対象ノードを格納する。
     // fireSlideToggleEx-x クラス指定されたボタン要素がブロック要素の場合には
     // next siblingを block 以外の inline 要素の場合には親要素の
     // next sibling 要素を slideToggleEx 対象とする。
130:   $target[i] = $(this).css("display")=="block" ? $(this).next() : $(this).parent().next();
131:   if ($target[i].length) {
132:    $(this).click(function(){
       // slideToggleEx を起動する。
133:     $target[i].slideToggleEx(j, duration, easing, fn);
134:     $(this).blur(); // ボタン要素のフォーカスを外す
135:    });
136:   }
137:  });
138: }
139: for (var j=0; j<9; j++){ // 0 から 8 迄の巡回処理
    // クリックイベントを登録するオブジェクト clk の i プロパティを作成し、
    // それに "fireSlideToggleEx-i"( i は 0~8 ) class 名のノードをもつ
     // jQuery インスタンスを登録する。これにより、ページ内にある
     // fireSlideToggleEx-n クラス名を持つノードを捉える。
140:  clk[j] = $(".fireSlideToggleEx-" + j);
141:    if (!clk[j].length) continue;
    // i 番名のノードがなければ loop の先頭行に戻って i++ する。
142:  func(j);
143:  clk[j].hover( // マウスオーバー/アウト時のボタン背景色の変更
144:   function(){$(this).css({backgroundColor:"pink"})},
145:   function(){$(this).css({backgroundColor:""})}
146:  )
    // マウスが押し下げられた場合の色替え
147:  .mousedown(function(){$(this).css('background-color','palegreen')})
148: }
149:});
150:});

▲ToTop

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 におけるアニメーションコードの解読 ( 3 )

このエントリイでは、jquery アニメーションで活用される基本的な次の 5 つの関数やメソッドを解読します。

genFx、show、hide、toggle、fadeTo

genFx( type, num )

引数:第 1 引数の type には show 、hide 又は toggle を代入し、第 2 引数 num には 1~3 を代入して利用します。

返値:オブジェクト

機能:配列 fxAttrs からプロパティ名を CSS style 属性名、プロパティ値を type とするオブジェクトを生成します。

この関数は、要素を 「非表示 → 表示へ」、「表示 → 非表示へ」、「表示/非表示の循環」とアニメートさせるために、animate メソッドの第 1 引数である prop オブジェクトを作成するものです。CSS スタイル値を toggle、show、hide とするので最初は違和感があります。しかし、アニメーションの全容を理解すれば、この関数の必要性や重要性が理解できます。

■genFx( type, num ) 関数コード解読
3765:var elemdisplay = {}, // トップレベルで 3 つの変数を定義する。
3766: timerId,
3767: fxAttrs = [
3768:  // height animations
3769:  [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
3770:  // width animations
3771:  [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
3772:  // opacity animations
3773:  [ "opacity" ]
3774: ];
3775:
3776:function genFx( type, num ){
3777: var obj = {};
    // each 適用対象は num==1 ならば ["height",・・・,"paddingBottom"]、
    // num==3 ならば ["height",・・・,"paddingBottom","width",・・・,"opacity"] となる。
3778: jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function(){
3779:  obj[ this ] = type; // each 適用対象ごとに type 値を代入し、
3780: });
3781: return obj; // obj={ height:type, marginTop:type, ・・・} となる。
    // つまり、返値は CSS style 属性名をプロパティ名とし、
    // type をプロパティ値とする複数のプロパティからなるオブジェクトとなる。
3782:}

▲ToTop

jQuery().show(speed,callback)

引数:アニメの継続時間、アニメ終了後に起動させるcallback 関数

返値:jQuery インスタンス

機能:言うまでもなく jQuery インスタンスに登録されている要素を表示させるメソッドです。

要素の display プロパティ値は、block だけではなく inline やその他の指定もあります( inline-block、table、inline-table 等々 )。ですから、「このメソッドは引数を与えなければ、単に display="block" とするだけの単純な関数だろう」との推測は、見事に裏切られてしまいます。

コードは2つの部分から成り、引数がある場合には animate メソッドが起動されますが、その解説は animate メソッドの項に譲り、ここでは引数がない場合の show メソッドの進行を跡付けます。

show メソッドの要点

第一は、hide メソッドも同様ですが、当該メソッドが同一インスタンスに対して複数回呼び出された場合の対応についてです。2 度目以降の呼び出しに対して省力化の仕組みが組み込まれており、そのことやその意義を理解する必要があります。

第二は、display プロパティについて、その算出( IE の場合には「カレント」と呼ぶことが多い)スタイル値と style オブジェクトのプロパティ値の区別を理解しなければならないということです。明示的なスタイル設定と暗黙裏に設定されるスタイル値は別物であることを踏まえなければなりません。

▲ToTop

以上のことを踏まえてコード進行を見てみます。

或る jQuery インスタンスに初めて show メソッドを適用する場合
  • あるインスタンスに対して初めて show メソッドを適用すると、jQuery.data メソッドの定義から、この時点では olddisplay 名で関連づけられた情報は存在しないので、style.display に空文字が代入されます(#3790-3792)。つまり this.style.display プロパティ値を初期化します。
  • 次に、jQuery.css メソッドにより当該要素の算出(カレント)display スタイル値を検証し、それが none だったら(#3794)トップレベル変数である elemdisplay オブジェクトの、当該要素のタグネーム名のプロパティ値の有無を調べます。この時、同一名称のタグに対して過去に show メソッドが呼び出されていなければ、このプロパティは存在していませんから、3800 行以下が履行されます。
    • 3800 行から3808 行では、show メソッドが対象としているタグ要素と同じ名称のタグを当該サイト内の body 部に追加し、そのカレントスタイル値を取得し、その値が none 以外ならばそのプロパティ値を、他方 none ならば "block" を、elemdisplay[ tagName ] に代入します。(#3808)
    • こうして elemdisplay[ tagName ]プロパティ値を利用するか、あるいはそれに none 以外のプロパティ値を代入した後に、インスタンスの各要素に olddisplay 名で display 値を関連づけます(#3811)。
    • ここに変数 display 値は、elemdisplay 値が存在した場合、すなわち同じタグ名称の他の要素に対して show メソッドが適用済みの場合(当該インスタンスタンス内の他の同一名称のタグに適用済みの場合を含む。対象としたインスタンス内のタグ名称が全て同じ場合には、2巡目以降の場合がこれに該当する)には、elemdisplay 値になります。
    • 他方、elemdisplay が存在しなかった場合には、当該サイト内において今対象としているタグ要素に暗黙裏に設定されている display スタイル値が、none ならば block に、none 以外であれば当該値( block、inline等々 )となります。
  • こうして当該要素にそれに相応しい display 値が与えられ、ブラウザがそれにより当該要素を描画し、ディスプレイ画面に表示させます。

▲ToTop

同じ要素に対して二度以上の show メソッドを適用した場合

次に、同じ要素に対して二度以上の show メソッドを適用した場合を跡付けます。

この場合には、既に一度目の show メソッド適用によって、変数 elemdisplay オブジェクトの当該要素と同一のタグネームプロパティ値が none 以外となっており、かつ当該要素には olddisplay 名で none 以外の display 属性値がテキスト文字列で関連づけられています。

この結果、3792 行により当該要素の style オブジェクトの display 値が none 以外の値に設定され、更に constant reflow を避けるために設けられた 3817 行により、再度 style.display 値が none 以外の block 等となります。

なお、constant reflow を直訳すれば「定期的な再流」ですが、これでは何のことか皆目分かりません。ネット検索してみましたが未解明です。

show インスタンスメソッドが複雑なコードになっている理由

ところで、当初は show メソッドは単純に if (this[i].style.display=="none") this[i].style.display="block" とするだけで用が足りると思っていましたから、どうしてこのように複雑なコードになっているのか、その理由をここで考えてみたいと思います。

まず、elemdisplay オブジェクトの存在意義です。どうしてトップレベルの変数に、対象としている要素と同一のタグネームプロパティを設け、その値を none 以外とするのでしょうか。あたかも同一タグ名称の要素の display プロパティ値を、1 つに統一する必要があるかのようです。

例えば、ある p タグの n 番目の要素( p(n)と名付ける )を show メソッドで表示させたとします。すると elemdisplay.p = "block" となり、p タグの n 番目以外の要素( p(m)と呼ぶ )に show メソッドを適用する場合には、#3799~3809 がスルーされ、#3811 で olddisplay 名で文字列 block が p(m) に関連づけられ、最後に #3818 によって p(m) の display 値が block となり、ブラウザ上に p(m) 要素が表示されます。

append 行為を同一名称の要素に対して 2 度以上引き起こさせない効率化が目的なのかもしれません。但し、それ以外の目的があるかどうか、また、あるとすれば何かについては解明できていません。

次に、同一タグ名の要素を わざわざ append するのは何故でしょうか。

それは次のような理由によると考えられます。

body に対象要素と同一タグ名称の要素を追加し、その算出(カレント) display 属性を調べれば、そのサイトにおいて、当該タグがどのような display 値をとっているのか把握することが出来ます。この方法によって、当該タグがサイト内で有する算出(カレント)display 値を取得することにしたのでしょう。

これに対して、olddisplay 名による要素への none 以外の文字列の関連づけは、分かりやすいものです。jQuery.data メソッドを利用するのは、一度当該要素の display 属性値を none 以外に登録した要素に対して、二度目以降はこの登録済み文字列を利用して容易に display 値を同じ値に設定するためです。show メソッドにおいても hide メソッドにおいても、jQuery.data メソッドがしっかり活かされています。

■$().show メソッドコード解読
3785: show: function(speed,callback){
3786:  if ( speed ) { // 引数 speed が定義されていれば
      // 「genFx("show",3 )を CSS スタイル値、speed をアニメ所要時間とし、
      // callback 関数をアニメ終了後に起動する animateメソッドを、
      // jQuery インスタンスから呼び出して実行し、その返値を返す。
3787:   return this.animate( genFx("show", 3), speed, callback);
3788:  } else { // 引数 speed が未定義か null 値の時、インスタンスの個々の要素に対して
3789:   for ( var i = 0, l = this.length; i < l; i++ ){
       // olddisplay なる名称で各要素ノードに関連付けられている値を取り出す。
       // 関連づけられた値がない場合には old = false となる。
       // 当該要素ノードに対する初めての show() 適用時には old は false と
       // なるが、二度目以降の呼び出し時には 3811 行で関連づけが定義済みなので
       // old = "block" となる。
3790:    var old = jQuery.data(this[i], "olddisplay");
3791:    // old があれば各要素ノードの display スタイル値に old を、
       // old が false ならば null を代入する。
3792:    this[i].style.display = old || "";
3793:    // 各要素ノードの display スタイル値が "none" ならば
3794:    if ( jQuery.css(this[i], "display") === "none" ) {
3795:     var tagName = this[i].tagName, display; // ローカル変数定義
3796:     // 当該要素ノードに対する初めての show() 適用時には
        // elemdisplay[ tagName ] プロパティは存在していないが、二度目以降の
        // 呼び出し時には 3808 行で定義済みなので存在していることになる。
        // elemdisplay オブジェクトに tagName プロパティがあれば、
3797:     if ( elemdisplay[ tagName ] ) {
3798:      display = elemdisplay[ tagName ]; // display 値をその値とする。
3799:     } else { // elemdisplay オブジェクトに tagName プロパティがなければ
         // tagName と同じ名称の要素ノードを作成し、body 追加後に参照を取得する。
3800:      var elem = jQuery("<" + tagName + " />").appendTo("body");
3801:      // 追加したノードの display 属性を取得する。
3802:      display = elem.css("display");
3803:      if ( display === "none" ) // もしもその値が "none" ならば
3804:       display = "block"; // "block" に変更する。
3805:      // body に追加したノードを削除し、
3806:      elem.remove();
3807:      // elemdisplay の tagName プロパティ値を display つまり "block" とする。
3808:      elemdisplay[ tagName ] = display;
3809:     }
3810:     // 各要素ノードに olddisplay 名の display 値 つまり "block" を関連づける。
3811:     jQuery.data(this[i], "olddisplay", display);
3812:    }
3813:   }
3814:
3815:   // Set the display of the elements in a second loop
3816:   // to avoid the constant reflow
3817:   for ( var i = 0, l = this.length; i < l; i++ ){
       // 各要素ノードの display スタイル属性を olddisplay 名で関連づけら
       // れている値("block")、又は null に設定する。
3818:    this[i].style.display = jQuery.data(this[i], "olddisplay") || "";
3819:   }
3820:
3821:   return this; // インスタンスを返す。
3822:  }
3823: },

▲ToTop

jQuery().hide(speed,callback)メソッドの解読

引数:継続時間、callback関数

返値:jQueryインスタンス

機能:言うまでもなく jQuery インスタンスに属する要素を隠蔽させるメソッドです。

このメソッドにおいても引数が与えられた場合には、animate メソッドが利用されますがここでは触れません。引数がない場合のみ解読します。

ある要素に初めて hide メソッドを適用した場合

インスタンスの個々の要素に対して、最初に hide メソッドが適用された場合において、既に当該要素に show メソッドが適用されていれば、当該要素に olddisplay 名で関連づけられた情報は block や inline となっており、一度も show メソッドが適用されていなければ、当該要素に olddisplay 名で関連づけられた情報は存在しません。

つまり、#3830 の変数 old には、block、inline などのスタイル値か、又は undefined が代入されます。そして old が undefined 値の場合、!old==true となり、勿論 old!=="none" が成立します。

こうして事前に show メソッドが適用されていない要素に対して、初めて hide メソッドを適用した時には、#3832 が実行され、data メソッドと css メソッドにより、当該要素の style.display 値、または算出(カレント)display 値が、olddisplay 名で当該要素に関連づけられます。
(関連づけられる値は block、inlineblock、inline 等となりますが、if 条件から none だけはあり得ません。olddisplay 値は block や inline などの表示用の値となり、none となることは決してありません。)

show メソッドの後に hide メソッドを適用した場合、あるいは hide メソッドを二度以上適用した場合

他方、show メソッドが適用された後に hide メソッドを適用した場合や、同一要素に対して二度目以降の hide メソッドを適用した場合には、old は未定義ではなくなっていますから !old=false となり、#3831~3832 は実行されないまま、#3837~3838 が実行され当該要素は非表示、あるいは非表示のママとなります。

上の行為の後に再び show メソッドを適用した場合

olddisplay 値は block や inline 等となっているので、3792 行により display 値はそのままとなります。よって #3794-3812 は通過し、3818 行が実行されて当該要素が表示されることになります。

■$().hide メソッドコード解読
3825: hide: function(speed,callback){
3826:  if ( speed ) { // speed があれば animate メソッドを実行する。
3827:   return this.animate( genFx("hide", 3), speed, callback);
3828:  } else { // speed がないか null の時
3829:   for ( var i = 0, l = this.length; i < l; i++ ){
       // 各要素に olddisplay 名で関連づけられているデータを変数 old に代入する
3830:    var old = jQuery.data(this[i], "olddisplay");
3831:    if ( !old && old !== "none" ) // old が false か "none"でない時には
        // 各要素ノードの display スタイル値を
        // olddisplay 名で各要素ノードに関連づける。
3832:     jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display"));
3833:   }
3834:
3835:   // Set the display of the elements in a second loop
3836:   // to avoid the constant reflow
3837:   for ( var i = 0, l = this.length; i < l; i++ ){
3838:    this[i].style.display = "none"; // 非表示を設定する。
3839:   }
3840:
3841:   return this; // インスタンスを返す。
3842:  }
3843: },
3844:

▲ToTop

jQuery().toggle(fn,fn2)

引数:fn,fn2

返値:jQueryインスタンス

機能:jQuery インスタンスに属する要素を表示/隠蔽させる循環メソッドです。

toggle メソッドはイベント処理にも登場します。それが最初の分岐(#3851)の2つの引数が共に関数の場合です。この場合には 3852 行が適用されてイベントの toggle 処理が行われます。

fn、fn2 が共に存在しない場合、あるいはいずれか一方以上が関数でない場合が、アニメ-ション処理で利用する toggle メソッドです。

アニメで利用する toggle メソッドは 2 つのケースに分かれます。

第 1 のケースは、fn、fn2が共に存在しない場合、あるいは第1引数が真偽値の場合です。このときにはインスタンスに登録されている各要素毎に次のことを行います。

  1. 最初の引数が真偽値で true ならばその値を state に代入します。
  2. bool がない場合(つまり引数が全くない場合)や、最初の引数が false の場合には、対象要素のhidden 属性の有無を調べ、あれば true、なければ false を state に代入します。つまり引数を全く指定しない場合や、fn == false と指定した場合には、当該要素の hidden 属性の有無によって、state に代入される値が決まります。hidden 属性があれば true、なければ false となります。(#3855)
  3. 次に、state が true ならば、jQuery(this).show() メソッドを実行し、false ならば、jQuery(this).hide() メソッドを実行します。(#3856)

第 2 のケースは fn、fn2 のいずれか一方以上が関数でない場合で、かつ最初の引数が真偽値でない場合です。このときには、"toggle" をアニメ用オブジェクトのプロパティ値とし、fn と fn2 を順に speed、easing として、インスタンスから animate メソッドを起動します。(#3858)この animate メソッドにおいて第 1 引数であるオブジェクトのプロパティ値を toggle とする場合の挙動は別途詳述します。

■ toggle インスタンスメソッド
3845: // Save the old toggle function
3846: _toggle: jQuery.fn.toggle,
3847:
3848: toggle: function( fn, fn2 ){
3849:  var bool = typeof fn === "boolean"; // fn が真偽値かどうかを bool に代入
3850:  // (1)fn も fn2 も関数ならば_toggle メソッドを実行する。
     //(このケースはイベント処理時に登場する)
3851:  return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
3852:   this._toggle.apply( this, arguments ) :
     // (2)引数がない場合、あるいはfn か fn2 のいずれか1つ以上が関数でない場合には、
      // (2-1)引数がないか、または fn が bool(真偽値)ならば
3853:   fn == null || bool ?
3854:    this.each(function(){ //各インスタンス毎に
        // bool が true ならば fn を、bool がない場合や false ならば当該要素の
        // hidden 属性有無(true or false)を変数 state に代入する。
3855:     var state = bool ? fn : jQuery(this).is(":hidden");
        // state が true ならば show メソッドを、
        // state が false ならば hide メソッドをそれぞれ起動する。
3856:     jQuery(this)[ state ? "show" : "hide" ]();
3857:    }) :
      // (2-2)fn が null でもなく真偽値でもない場合animateメソッドを実行する。
3858:    this.animate(genFx("toggle", 3), fn, fn2);
3859: },

▲ToTop

jQuery().fadeTo()

引数:speed、to、callback

返値:jQueryインスタンス

機能:不透明度を to、speed を duration、callback をアニメ終了後に実行される関数とする、animate メソッドを起動します。

■ fadeTo メソッド
3861: fadeTo: function(speed,to,callback){
3862:  return this.animate({opacity: to}, speed, callback);
3863: },

▲ToTop

自作 jQuery プラグイン :animatedPopup 後に別のアニメーションを連続起動する

ここのアニメーションは IE では動きません。

jquery.js を使用した Animation 連鎖にチャレンジ

animatedPopup 起動後に、続けて別の Animation を連続起動してみる。

jQuery では連続アニメーションを容易に実行させることが出来る。例えば animatedPopup による表示を終えた後に、その要素を例えば画面内で移動するとか、次第に不透明化しながら消滅させるとか。表示領域を縮小/拡大するとか、様々なアニメーションを連続起動させることが可能だ。

あるいはまた複数のアニメーションを同時に履行させることも出来る。更にアニメーション終了後に callback 関数を起動させて何らかの処理をさせることも可能だ。自作プラグイン animatedPopup の連続起動も行えるだろう。

このエントリイではそんな複数アニメーションの連続履行を試みる。

連続アニメ 1. animatedPopup 後の要素を画面内で移動する。

animatedPopup 関数起動が終了すると、ユーザーが隠蔽操作を行わない限り、その要素は画面内に表示され続ける。これを活かして当該要素を自在に画面内に移動させてみる。

表示するコンテンツは先のエントリイ同様に他サイトの写真素材を活用し、画面内を次のように移動させる。まず画面左上に animatedPopup 表示する。その後、slideUp、slideDown で遊んでから、画面内を右上 → 右下 → 左下と時計回りに連続して移動させ、最後に最初の位置に戻してから fadeOut させてみる。

移動速度は 0.5、1、1.5、2 秒と次第に遅くし、それぞれの動きを加減速する easing も微妙に変化させ、消滅には 2.5 秒を掛けてみた。

この一連の動きを実行するには、下のボタンをクリックするだけでよい。この一連の自動実行アニメーションを楽しんで(?)戴きたい。

(出典は pixta

▲ToTop

連続アニメ 2. animatedPopup 後複数のアニメーションを同時起動する。

今度は animatedPopup によるアイテム表示後に、それを画面内で横移動させながら、フェードアウトさせ、直ぐにフェードインさせ、その後上へ移動させてから消滅させる。

(出典は pixta

連続アニメ 3. 2つの animatedPopup とその後のアニメーションを活かしつつ、相互に連続起動する。

下のボタンをクリックすると上で定義した 2 つのアニメーションを連続実行する。もう一度同じボタンを押した場合には、連続実行の順番を変えて実行するようにした。

▲ToTop

ここで実現した連続アニメーションのコード解説

ここで行った連続アニメーション作成の過程において、animatedPopup プラグインは更に進化を遂げざるを得なかった。元々連続アニメーションを想定していない仕様だったからだ。

その拡張に関する詳細は別項にて述べるとして、ここではこのエントリイで実現した連続アニメーションのコードについてメモとして解説しておきたい。

連続アニメーションのコード作成過程において、jquery.js のアニメーションコードをますます深く学習せざるを得なかったし、作成途上でスクリプトに工夫を加えた箇所も多数に上ったからである。

■このエントリで実現した連続アニメーションのコード
  // animatedPopup プラグインのインクルード
 1:(function(){var s=document.createElement('script');s.src='http://blog-imgs-31.fc2.com/h/k/o/hkom/my_Utilize_jQuery2.js';s.type='text/javascript';document.getElementsByTagName('head')[0].appendChild(s);})();
   // ボタンの色づけ
 2: $(".btn711").css("backgroundColor","burlywood")
 3:  .mousedown(function(){$(this).css("backgroundColor","plum")})
 4:  .mouseup(function(){$(this).css("backgroundColor","burlywood").blur()});
   // 表示するコンテンツの作成
 5: var contents0="次のアニメーションを起動します。";
 6: var contents1='<img src="http://image.pixta.jp/image/thumb/51/14d687c77d91104811600b9d9916948a.jpg" width=450 height=301 />';
 7: var contents2='<img src="http://image.pixta.jp/image/thumb/41/bf5a19a4278538425a476ed1e113f0a9.jpg" width=450 height=316 />';
 8: // 画像先読み
 9: $("<div></div>").css("display","none").prependTo(document.body).html(contents1+contents2);
10:
11:$(function(){
   // animatedPopup プラグイン終了後に履行する関数定義その1
12: startAfterAnima = function(){
13:  var ani1=$(window).width()-$(this).outerWidth()+"px";
14:  var ani2=$(window).height()-$(this).outerHeight()+'px';
15:  $("#dispElem").slideUp("slow").slideDown()
16:  .animate({"left":'+='+ani1},500,"linear")
17:  .animate({"top":"+="+ani2},{duration:1000,easing:"swing"})
18:  .animate({"left":"-="+ani1},{duration:1500,easing:"easeInOutExpo"})
19:  .animate({"top":"-="+ani2},{queue:true,duration:2000,easing:"easeInOutSine"})
20:  .animate({"opacity":0},2500,"swing",function(){$(this).empty().hide().css({"opacity":1})});
21: }
   // animatedPopup プラグイン終了後に履行する関数定義その2
22: var startAfterAnima2 = function(){
23:  var ani=$(window).scrollTop();
24:  $("#dispElem").slideUp("slow").slideDown("slow")
25:  .animate({"left":"0px"},{queue:false,duration:2000,easing:"swing"})
26:  .fadeOut("slow").fadeIn("slow").animate({"top":ani},{queue:true,duration:2000,easing:"easeInOutSine"})
27:  .animate({"opacity":0},2500,"swing",function(){$(this).empty().hide().css({"opacity":1})});
28: }
29: // 各ボタンクリック時の animatedPopup 起動
30: $("#btn711-0").animatedPopup(contents0,["c","c"],1500,null,null,true);
31: $("#btn711-1").animatedPopup(contents1,["l","t"],2000,"easeOutElastic",null,null,true,startAfterAnima);
32: $("#btn711-2").animatedPopup(contents2,["c","c"],2000,"easeInOutExpo",null,null,true,startAfterAnima2);
 // 連続してanimatedPopup メソッドを起動する処理
33: // 3 つのボタンの jQuery インスタンスを配列に登録
34: var ary = [$("#btn711-0"),$("#btn711-1"),$("#btn711-2")];
   // 次のアニメーションを起動させる関数
35: var nextFunc =function(jQelem){
36:  if (!$("#dispElem").queue("fx").length){
37:   jQelem===ary[0] ? clearInterval(ival) : clearInterval(ival2);
38:   jQelem.trigger("click");
39:  }
40: }
41: var ival,ival2;
   // 3 番目のボタンに対する toggle イベント定義
42: $("#btn711-3").toggle(function(){
43:  $(this).children(0).text("リゾートプール+清水寺");
44:  ary[1].trigger("click");
    // 2つの待機関数を定義
45:  ival = setInterval(function(){nextFunc(ary[0]);},1000);
46:  ival2 = setInterval(function(){nextFunc(ary[2]);},1500);
47: },function(){
48:  $(this).children(0).text("清水寺+リゾートプール");
49:  ary[2].trigger("click");
50:  ival = setInterval(function(){nextFunc(ary[0]);},1000);
51:  ival2 = setInterval(function(){nextFunc(ary[1])},1500);
52: });
53:});

▲ToTop

コード内に記述したコメントでかなり説明になっていると思うが、苦労した点を中心にメモしておきたい。

アニメーション同時進行

幾つかの箇所でアニメーションの同時起動を試みた。queue:false に設定している箇所が同時進行のためのコードで、具体的には移動しながら slideUp/Down を行わせた。

アニメーション終了後の別アニメーションの起動

jQuery の animate インスタンスメソッドの complete 属性を活用して、animatedPopup メソッドが終了した後に別のアニメーションを起動させた。#124-140 で定義した関数が complete プロパティ値となる関数である。

2 つの animatedPopup 連続起動に最も苦労した

animatedPopup プラグインは jQuery インスタンスとして定義した。そしてここで扱った 2 つの写真表示アニメーションは、それぞれの呼び出し元が別々のボタンであり、animatedPopup には巧みに返値を設定することが出来ていない。つまりメソッドチェーンが使えない。

そこで jquery.js の イベント処理メソッドとして定義されている trigger メソッドを使って、異なる呼び出し元から呼び出される animatedPopup メソッドを連続起動させるようにした。

さて要点はタイマー設定だ。単純に 2 つの animatedPopup を並べるだけでは最後のそれしか作動しないのだ。コード進行はアニメーション実行とは無関係に次行へ次行へと進んでしまうから、前のアニメーションを待つことなく次のアニメーションが起動されてしまい、結局最後のアニメーションだけが表示されるのだ。

このため或るアニメーションが動作している間は、次のアニメーションが起動されないようにしなければならない。

そこで、タイマーを使って次のアニメーション起動を抑制することにした。

そこで問題となるのは、アニメーションが進行中であることを Javascript インタープリタに知らせる方法だ。jQuery インスタンス毎に、メソッド queue("fx") に animation が登録されることを利用する方法が或る書籍に書いてあったのでそれを使ってみた。

2 つ以上の animatedPopup の連続起動を別の方法で行ってみる

もっと巧い方法はないのだろうか、と思いたち本家サイトを改めて眺めてみた。すると、極めて簡単な方法が実は存在していることを思い知らされた。それは jQuery インスタンスの add メソッドを利用するものだ。こんな風にaddメソッドが使えたのか!───と感心してしまった。

しかし、この方法は 1 つの絶対配置要素を使ってアニメーションを実現している animatedPopup では巧く採用することが出来ないでいる。

jQuery の挙動を解読する(35):Traversing メソッドについて(1) ──jQuery解読(52)

何回かに分けて Traversing に分類されているインスタンスメソッドの解読を行います。

全体の項目とこのエントリイの項目
  • eq()、slice()、is()、filter()、closest()、not()、add()
  • children()、contents()、find()、next()、nextAll()
  • offsetParent()、parent()、parents()、prev()、prevAll()、siblings()、andSelf()、end()

このエントリイでは、本家サイトにおいて Traversing に分類されているインスタンスメソッドの内、次のメソッドを解読対象とします。

eq()、slice()、is()、filter()、closest()、not()、add()

eq()、slice() メソッド解読

eq() メソッドはインスタンスに登録されている要素ノードから、i+1 番目の 1 つのノードを取り出します。

そのために使用される slice() メソッドはインスタンス内に登録されている要素ノードから、連続した一部を取り出すためのメソッドです。このメソッドを適用すると、元のインスタンスの一部のプロパティを保持する新たなインスタンスが得られます。このとき、pushStack() メソッドにより、新たなインスタンスの selector プロパティには抽出経過を記録する文字列が代入されます。

■ eq() メソッド……インスタンスの配列 like なプロパティから i 番目の要素を取り出す
495: eq: function( i ) {
496:  return this.slice( i, +i + 1 );
497: }, 

■ slice() メソッド …… インスタンスの配列 like なプロパティからその連続した部分を取り出す
499: slice: function() {
500:  return this.pushStack( Array.prototype.slice.apply( this, arguments ),
501:   "slice", Array.prototype.slice.call(arguments).join(",") );
502: },
このエントリイ上で eq()、slice() を試してみる

左のボタンをクリックすると、最初の pre タグの背景色が teal になり、もう一度このボタンをクリックすると元に戻ります。

左のボタンをクリックすると 6 番目から 9 番目迄の 4 つの p タグ背景色が teal に変わり、もう一度クリックすると元に戻ります。

尚、ボタンをクリックした際には、mousedown 時にそのボタンの背景色が変わり、mouseup すると元に戻るように設定しました。

is()、filter() メソッド解読

is() メソッドは或るノードがインスタンス内に登録されているかどうかを調べます。filter() メソッドはその名の通りインスタンス内に登録されているノードを対象として、一定の条件でフィルタリングを行います。

■ jQuery(a).is(s) …… a にマッチする要素ノード内に s にマッチする要素があるかどうか調査する。
401: is: function( selector ) {
    // 引数 selector が存在し、かつ jQuery インスタンスの中に selector に
    // マッチするノードが 1 つ以上あれば true を、なければ false を返す。
402:  return !!selector && jQuery.multiFilter( selector, this ).length > 0;
403: },

■ jQuery(s,c).filter(selector) メソッド
349: filter: function( selector ) {
    // selector が関数の場合とそうでない場合とに分けて pushStack() メソッ
    // ドの結果を受け取る。
350:  return this.pushStack(
351:   jQuery.isFunction( selector ) && // selector が関数ならば
     // インスタンスの各プロパティに登録されている要素ノードに対して
352:   jQuery.grep(this, function(elem, i){
353:    return selector.call( elem, i ); // 順番に関数を適用し結果を返す。
354:   }) || // あるいは
355:   // 関数でない場合には、multiFilter メソッドを使用して、インスタンス
     // に登録されている要素ノードから selector にマッチする要素を取り出す。
356:   jQuery.multiFilter( selector, jQuery.grep(this, function(elem){
357:    return elem.nodeType === 1;
358:   }) ), "filter", selector );
359: },
このエントリイ上で is()、filter() を試してみる

左のボタンをクリックすると、この p タグの contents に「左のボタン」という文字列があるかどうか調べ、結果を animate 表示 します。当然それは存在しているのでtrueであることを示す文字列がアニメーションポップアップで表示されます。

尚、このボタンは単純なクリックボタンで toggle にはなっていません。表示された popup を消去するにはその popup 内にある×をクリックします。またボタンが複数回クリックされた場合には、popup を即ぐに消し去ってから新たなポップアニメーションが起動します。

また、このボタンだけは IE8 では作動しません。おそらくは IE8 の Animation に関するバグのためだと思われますが、正確な原因は不明です。

左のボタンをクリックすると「 左のボタン 」という文字列を含む全ての p タグ背景色が teal に変わり、もう一度クリックすると元に戻ります。

尚、ボタンをクリックした際には、mousedown 時にそのボタンの背景色が変わり、mouseup すると元に戻るように設定しました。

closest() メソッド解読

これは 1.3.x から追加された新しいメソッドです。jQuery(s,c)にマッチする要素( A )を対象として、closest(selector) を適用すると、A 及びその親ノードの中から、selector フィルタにマッチするノードをを取得します。

要点はインスタンスが参照しているノードからだけではなく、その親ノードからも条件に合致するノードを探す点にあります。イベントオブジェクトがバブリングして親要素にも達するように、このメソッドは親要素をも対象としている点が注目されます。「 closest(近親者)」と名付けられた所以です。

■ jQuery(s,c).closest(selector) メソッド
361: closest: function( selector ) {
    // jQuery.expr.match.POS は /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/
    // である( #1663 から )。そしてこの正規表現は次のような文字列となる。
    //「 コロンの後に nth から odd までのいずれかの文字列があり、その後に(数字)が 
    // 1 回以下続き、その後に - ではない 1 文字があるか行末まで何もない 」
    // ── selector がこのような文字列の場合には、 jQuery(selector) の
    // 結果を変数 pos に代入し、そうでない場合には null 値を代入する。
362:  var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null,
363:   closer = 0;
364:
365:  return this.map(function(){ // インスタンスに map メソッド処理を行って返す。
     // この結果、インスタンスの各要素及びその親ノードの中から、selector 条件に
     // 合致するノードが抽出され、それらをプロパティとする jQuery インスタンス
     // が返される。
366:   var cur = this; // この this は map メソッド内にあるので、
             // インスタンスに登録されている個々の要素ノードを指す。
367:   while ( cur && cur.ownerDocument ) { // cur 及びその document ノードが
      // ある限り、変数 pos があれば cur が pos の何番目かにあるかどうか
      // チェックし、pos がなければ cur にマッチする要素の中に selector
      //  にマッチするものがあるかどうかをチェックし、いずれかが true であれば、
368:    if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) {
       // cur 要素に closest と言う名前と closer 値を関連づける。
369:     jQuery.data(cur, "closest", closer);
370:     return cur; // curを返す。
371:    }
372:    cur = cur.parentNode; // cur をその親ノードとし、closer 値を増加する。
373:    closer++;
374:   }
375:  });
376: },

左のボタンをクリックすると「 メソッド 」という文字列を含む全ての p タグ背景色が teal に変わり、もう一度クリックすると元に戻ります。

尚、ボタンをクリックした際には、mousedown 時にそのボタンの背景色が変わり、mouseup すると元に戻るように設定しました。

not()、add() メソッド解読

not メソッドは名前からその機能が推測しにくいメソッドです。is() メソッドは「あるかないか」の真偽値を返しますが、notメソッドの返値は jQuery オブジェクトです。

このメソッドは jQuery インスタンスから、selector で指定された条件を満たさないノードを抽出します。

他方、add() メソッドはその名の通り、jQuery インスタンスに selector 条件に合致するノードを「追加」します。

■ jQuery(s,c).not(selector) メソッド
378: not: function( selector ) {
379:  if ( typeof selector === "string" ) // selector が文字列の時
380:   // test special case where just one selector is passed in
     // isSimple は #33 から /^.[^:#\[\.,]*$/。つまり最初に何か 1 文字があり、
     // 次に : でも、# でも、 [ でも、.でも、 ,でもない文字がゼロ個以上続く
     // 文字列を意味する。つまり、何らかのフィルターでもなく、クラス名でもなく、
     // id でもなく、複数のセレクタでもない単純な文字列を意味する。
381:   if ( isSimple.test( selector ) ) // selector が上のような条件の文字列ならば
      // インスタンスから selector 条件を満足するノードを削除し、その結果を
      // jQuery インスタンスを返す。
382:    return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector );
383:   else // 単純ではない文字列だったら、インスタンスから selector 条件に
      // 合致するノードを抽出して selector に代入する。
384:    selector = jQuery.multiFilter( selector, this );
385:  // selector に length 値があって、最後の selector 値が定義されていて、
    // selector がノードではなければ true を返す。
386:  var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
387:  return this.filter(function() { // インスタンスに登録されている各ノード
     // に対してfilter メソッドにより引数の関数を適用する。filter メソッド
     // 内では grep メソッドが条件に合致するノードだけを抽出する。
     // これにより各ノードが selector に合致しない場合には true が返され、
     // 合致した場合には false が返される。この結果 selctor 条件に合致しない
     // ノードだけが残され、これをプロパティとする jQuery インスタンスが返される。
388:   return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
389:  });
390: },

■ jQuery(s,c).add(selector) メソッド
392: add: function( selector ) {
393:  return this.pushStack( jQuery.unique( jQuery.merge(
394:   this.get(), // merge メソッドの第 1 引数は配列でなければならない。
395:   typeof selector === "string" ?
      // merge メソッドは第 2 引数が配列 like なプロパティ値を持つオブ
      // ジェクトであっても、配列そのものでも同様に扱える。
396:    jQuery( selector ) :
397:    jQuery.makeArray( selector )
398:  )));
399: },

jQuery の挙動を解読する(33):DOM Manipulate メソッド解読──jQuery解読(50)

Contents
  • domManip() メソッド解読
  • 各種 DOM Manipulate メソッドの性質と分類
  • 各種 DOM Manipulate メソッドの例を試す
  • 各種 DOM Manipulate メソッドを解読する───親ノード追加系
  • 各種 DOM Manipulate メソッドを解読する───子ノード追加系
  • 各種 DOM Manipulate メソッドを解読する───兄弟ノード追加系
  • 各種 DOM Manipulate メソッドを解読する───置換系

このエントリイでは DOM 要素操作 に係るインスタンスメソッドに焦点を当て、それらのメソッドを解読します。

対象とするメソッドは、domManip()、wrapAll()、wrapInner()、wrap()、append()、prepend()、before()、after() 、appendTo()、prependTo()、insertBefore()、insertAfter()、replaceWith()、replaceAll()、text()、html() です。

domManip()

まず、DOM 走査の基本的な役割を担う domManip() メソッドから解読します。

このメソッドは args 内の html 文字列によってインスタンスにノードを追加する際に、(1) clean メソッドを適用して html 文字列の記述ミスを正し、(2) またブラウザバグを補正すると共に、(3) DocumentFragment を使用することにより script タグをインスタンスに追加する、以上のことを行います。

■ domManip() インスタンスメソッド解読
  // 引数は順に、1:走査対象となるノードを要素とする配列、2:table 有無の指定、
  // 及び 3:適用するメソッドで、返値は jQuery インスタンスである。
514:domManip: function( args, table, callback ) {
515: if ( this[0] ) { // インスタンスに最初の要素があれば
    // 3 つの変数を定義する。まず文書断片( DocumentFragment )を作る。ここに、
    // this[0] が document ノードの時には owwnerDocument 値が null となる
    // ため < || this[0] > が必要となる。いずれにしても document ノードに
    // 文書断片を作成し、それへの参照を fragment 変数に代入する。
516:  var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(),
     // 引数 args を clean メソッドでチェックし return 値を変数 scripts に代入
     // する。ここに clean メソッド #961 から返される値は配列であり、その要素は
     // args 内に存在していた script タグである。
517:   scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ),
     // 上の clean メソッドにより args 配列要素の script タグ以外のタグ内にあった
     // script タグが削除された後に fragmentノードの chileNodes となっている。
     // ( #957 )。その中の第一子ノードを 変数 first に参照させる。
518:   first = fragment.firstChild;
519:  // このブロックは args 内に記述されていた script タグを、インスタンスの各要素
    // ノードから呼び出される実行される callback 関数の引数にするためのもの。
520:  if ( first ) // 第一子ノードが取得できれば
     // インスタンスに格納されている要素ノードの数だけ巡回処理する。
521:   for ( var i = 0, l = this.length; i < l; i++ )
      // root 関数で table ノードの場合の処理を行い、call の第 1 引数とし、
      // this に格納されている要素ノード数が 2 以上か イテレート変数が
      // 1 以上ならば fragment ノードの cloneNode をイベント付きで作成して
522:    callback.call( root(this[i], first), this.length > 1 || i > 0 ?
        // 第 2 引数とし、要素ノード数が 1 以下か i = 0 の時には
        // fragment ノード自体を第 2 引数とする。
523:      fragment.cloneNode(true) : fragment );
524: 
525:  if ( scripts ) // scripts が取得できたらば
     // その 1 つ 1 つを evalScript メソッドで評価する。
526:   jQuery.each( scripts, evalScript );
527: }
528: // 以上の処理を終えた this インスタンスを呼び出し元に返す。
529: return this; // また this[0] がない場合には this をそのまま返す。
530: 
531: function root( elem, cur ) {
    // 引数 table が true で、要素ノード=this[i] が table ノードで
    // cur が tr ノードならば
532:  return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ?
     // 取得できた最初の tbody ノードを返すか、取得できなければ tbody 要素ノードを
     // elem ノードに追加して return する。
533:   (elem.getElementsByTagName("tbody")[0] ||
534:   elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
     // その他の場合には elem 自身=this[0] を return する。
535:   elem;
536: }
537:}

▲ToTop

各種 DOM Manipulate メソッドの性質と分類

DOM 操作を行う jQuery インスタンスメソッドは次のように沢山あります。─── $(s,c).wrapAll(html)、$(s,c).wrapInner(html)、$(s,c).wrap(html)、$(s,c).append(elem)、$(s,c).prepend(elem)、$(s,c).before(elem)、$(s,c).after(elem)、$(s,c).appendTo(elem)、$(s,c).prependTo(elem)、$(s,c).insertBefore(elem)、$(s,c).insertAfter(elem)、$(s,c).replaceWith(elem)、$(s,c).replaceAll(elem)、$(s,c).text(str)、$(s,c).html(html) 等々。

これらの 15 個のメソッドの共通点は、jQuery インスタンス内に参照が取得されている要素ノードと、各のメソッドの引数である html や elem 等によって定まるノードとの、二者の組み合わせ方を定めることです。これらのインスタンスメソッドは、2 つの(あるいは 2 つの群の) DOM 要素間の前後関係(つまり兄弟関係)、包含/被包含関係(親子関係)、あるいは置換/被置換関係を定義づけます。

まず、組み合わせの対象となる 2 つ( 2 群 )の位置関係を整理しておくべきでしょう。分かりやすく表示するために、インスタンス内で参照付けられているノード群をA、引数によって参照付けられるノードをBとします。

親ノード追加系
  • $(s,c).wrapAll(html)……A[0]~A[last] までの全体を B で包含する。A[i] = B.childNodes[i]
  • $(s,c).wrapInner(html)……A の子ノード C を B で囲む。B.childNodes =A[i].childNodesとなる。
  • $(s,c).wrap(html)……B を個々の A の親ノードとする。A[i].parentNode = B
子ノード追加系
  • $(s,c).append(elem)……B を個々の Aの最後の子ノードとする。A[i].lastChild = B
  • $(s,c).prepend(elem)……B を個々の A の最初の子ノードとする。A[i].firstChild = B
  • $(s,c).appendTo(elem)……A を B の最後の子ノードとする。B.lastChild = A
  • $(s,c).prependTo(elem)……A を B の最初の子ノードとする。B.firstChild = A
兄弟ノード追加系
  • $(s,c).before(elem)……B を個々の A の直前の兄弟ノードとする。A[i].previousSibling = B
  • $(s,c).after(elem)……B を個々の A の直後の兄弟ノードとする。A[i].nextSibling = B
  • $(s,c).insertBefore(elem)……A を B の直前の兄弟ノードとする。A = B.previousSibling
  • $(s,c).insertAfter(elem)……A を B の直後の兄弟ノードとする。A = B.lastSibling
置換系
  • $(s,c).replaceWith(elem)……A を B で置換する。A[i] → B
  • $(s,c).replaceAll(elem)……B を A で置換する。A[i] ← B
  • $(s,c).text(str)……A の 子要素を B で置換する。A[i].childNodes = B
  • $(s,c).html(html)……A の 子要素を B で置換する。A[i].childNodes = B

▲ToTop

各種 DOM Manipulate メソッドの例を試す

解読の前に各種 DOM Manipulate メソッドの適用例をこのページ上で見てみます。それぞれのメソッドの働き方や差異を見るにはそれが手っ取り早いからです。

適用例の説明
  • 下のボタンは各種 DOM Manipulate メソッドを実行するためのもの。15 のいずれかをクリックすると、ボタンの下にある薄緑色のボックス内で各種メソッドの効果を表示します。
  • メソッドの適用効果を消去するにはもう一度同じボタンをクリックします。
  • メソッドの適用対象は、薄緑色のボックス内に配置した 3 つの p タグです。これをボタン上では $(this) と表示しました。3 つの pタグはピンクの枠で示していますが、それらを対象として各種 DOM Manipulate メソッドを適用します。
  • 他方、働き方を逆転させるメソッドの場合には、メソッド起動要素がサイト内に存在していなければ成りませんので、そのための要素として DIV タグ( class="targetDiv" )を用意しました。

メソッドの引数として次の html 文字列を準備しました。

  • "<div style='background-color:maroon;border:4px dotted gray;margin:2px;padding:2px;'></div>"
  • "<p style='background-color:maroon;border:4px dotted gray;margin:2px;padding:2px;'>これはボタンをクリックしたことにより追加されたノードです。</p>"
  
   
   
   

(第1 pタグ)これは各種 DOM Manipulate メソッドの働き方を視覚的に見やすくしたサンプルです。

(第2 pタグ)このブロックを包含する div タグがあり、その中に複数の 3 つの p タグと 2 つの div タグを配置し、3 つめのパラグラフには画像を配置しました。

(第1 divタグ)3 つの p タグを操作対象として各種のメソッドを適用します。

(第3 pタグ) これは画像を含むパラグラフです。

(第2 divタグ)
逆転メソッドで使用する div タグ

各種 DOM Manipulate メソッドを解読する───親ノード追加系

$(s,c).wrapAll(html) メソッド解読

このメソッドは、jQueryインスタンスが参照するノード全体を、 html 文字列によってサイトに追加されるノードの子ノードにします。

■ $(s,c).wrapAll(html) メソッド
219: wrapAll: function( html ) {
220:  if ( this[0] ) {
221:   // The elements to wrap the target around
     // html 文字列で指定されたノード群を document 内で検索し、マッチ
     // したノードの clone ノードを作り、変数 wrap に代入する。
222:   var wrap = jQuery( html, this[0].ownerDocument ).clone();
223:   // インスタンスの最初のノードに親ノードがあれば、
224:   if ( this[0].parentNode )
      // clone ノード ( wrap ) をインスタンスの、最初の配列もどきプロ
      // パティが参照するノード ( this[0] ) の前に配置する。
225:    wrap.insertBefore( this[0] );
226:
227:   wrap.map(function(){
      // この this は mapメソッド内にあり、map メソッドが wrap から
228:    var elem = this; // 呼び出されているので wrap を参照する。
229:
230:    while ( elem.firstChild ) // 最も深い階層にある最初の
231:     elem = elem.firstChild; // 子ノードを elem に代入し、
232:
233:    return elem; // その最後の子ノードを返す。
     // 返された最深層の最初の子ノードの中に、this を最後の子ノードとして追加する。
     // なおここの this はインスタンスから呼び出される wrapAll メソッド内にある
234:   }).append(this); // ので、インスタンスを参照する。
235:  }
236:
237:  return this; // インスタンスを返す。
238: },
$(s,c).wrapInner(html) 解読

このメソッドはインスタンスの各々の子ノードを html 文字列による追加ノードで包含します。

■ $(s,c).wrapInner(html) メソッド
240: wrapInner: function( html ) {
241:  return this.each(function(){ // インスタンスの各々のノードに対して
     // 当該ノードの子ノード jQuery( this ).contents() を html により
     // 挿入されるノードで包含する。なお、contents() は #1186 で定義
     // されているインスタンスメソッドである。
242:   jQuery( this ).contents().wrapAll( html );
243:  });
244: },
$(s,c).wrap(html) 解読

このメソッドはインスタンスが参照する各々のノードを html 文字列による追加ノードで包含します。

■ $(s,c).wrap(html) メソッド
246: wrap: function( html ) {
    // インスタンスの各々のノードを巡回処理し結果を返す。
247:  return this.each(function(){
     // 各々のノードを html 文字列による追加ノードの子ノードとする。
248:   jQuery( this ).wrapAll( html );
249:  });
250: },

▲ToTop

各種の DOM Manipulate メソッドを解読する───子ノード追加系

子ノードを追加する 最初の 2 つのメソッドでは共通して domManip メソッドが使われています。また、appendTo メソッドと prependTo メソッドはそれぞれ append と prepend メソッドを利用して対象を逆転させているだけですから、結局 4 つのメソッドで domManip メソッドが使われていることになります。

これは、誤記や table に係る IE のバグ対策を施さないと追加により、インスタンスが指し示すノードが文法的におかしくなってしまうことがあるためです。

■ 子ノード追加系 メソッド
▼ append() メソッド
252: append: function() {
253:  return this.domManip(arguments, true, function(elem){
254:   if (this.nodeType == 1)
      // この this は domManip メソッドの定義から、インスタンス内で指し
      // 示されている個々のノードを指す。( #522 )その個々のノードの
      // 最後の子ノードとして elem により定義されるノードを挿入する。
255:    this.appendChild( elem );
256:  });
257: },
258:

▼ prepend() メソッド
259: prepend: function() {
260:  return this.domManip(arguments, true, function(elem){
261:   if (this.nodeType == 1)
      // この this は domManip メソッドの定義から、インスタンス内で指し
      // 示されている個々のノードを指す。( #522 )その個々のノードの
      // 最初の子ノードの前に、elem により定義されるノードを挿入する。
262:    this.insertBefore( elem, this.firstChild );
263:  });
264: },

▼insertBefore() 及び insertAfter() メソッド
// 以下のブロックの解読は別途こちらのエントリイ ( jQuery の挙動を解読する(31):
jQuery.each({ nameN:fnN(){}},function(){}) によるメソッド登録──jQuery解読(48) )
で行いました。
1198:jQuery.each({
1199: appendTo: "append",
1200: prependTo: "prepend",
1201: insertBefore: "before",
1202: insertAfter: "after",
1203: replaceAll: "replaceWith"
1204:}, function(name, original){
1205: jQuery.fn[ name ] = function( selector ) {
1206:  var ret = [], insert = jQuery( selector );
1207:
1208:  for ( var i = 0, l = insert.length; i < l; i++ ) {
1209:   var elems = (i > 0 ? this.clone(true) : this).get();
1210:   jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
1211:   ret = ret.concat( elems );
1212:  }
1213:
1214:  return this.pushStack( ret, name, selector );
1215: };
1216:});

▲ToTop

各種の DOM Manipulate メソッドを解読する───兄弟ノード追加系

兄弟ノードを追加する最初の 2 つのメソッドでも共通して domManip メソッドが使われ、その中では clean メソッドが呼び出されます。また、insertBefore メソッドと insertAfter メソッドはそれぞれ before と after メソッドを利用して対象を逆転させているだけですから、結局 4 つのメソッドで domManip メソッドが使われていることになります。

このような回り道をするのは、clean メソッドを使って誤記や table に係る IE のバグ対策を施さないと、ノード追加によって、インスタンスが指し示すノードが文法的におかしくなってしまうことがあるためです。

■ 兄弟ノード追加系 メソッド
▼ before() メソッド
266: before: function() {
267:  return this.domManip(arguments, false, function(elem){
     // この this は domManip メソッドの定義から、インスタンス内で指し
     // 示されている個々のノードを指す。( #522 )その個々のノードの
     // 前に、elem により定義されるノードを兄弟要素として挿入する。
268:   this.parentNode.insertBefore( elem, this );
269:  });
270: },
271:

▼ after() メソッド
272: after: function() {
273:  return this.domManip(arguments, false, function(elem){
     // この this は domManip メソッドの定義から、インスタンス内で指し
     // 示されている個々のノードを指す。( #522 )その個々のノードの
     // 次の兄弟ノードの前に、elem により定義されるノードを挿入する。
     // つまり個々のノードの後ろに elem を兄弟ノードとして挿入する。
274:   this.parentNode.insertBefore( elem, this.nextSibling );
275:  });
276: },
// insertBefore と insertAfter の 2 つのメソッドに関しては、直上の
// 「子ノード追加系」でリストアップした #1198-1216 を参照のこと。

▲ToTop

各種の DOM Manipulate メソッドを解読する───置換系

replaceWith(elem) メソッドは極めて簡単なコードで定義されています。インスタンスが指し示す個々の要素ノードの直後に兄弟要素 elem を追加してから、当該要素ノードをを削除します。

■ 置換系 メソッド
▼ replaceWith() メソッド
491: replaceWith: function( value ) {
492:  return this.after( value ).remove();
493: },

▼ replaceAll() メソッド
// replaceAll メソッドに関しては、こちらを参照のこと。

▼ text() メソッド
201: text: function( text ) {
    // 引数がオブジェクトでもなく空でもなければ
202:  if ( typeof text !== "object" && text != null )
     // インスタンスが指し示す個々の要素の子ノードを削除してから、
     // 0 プロパティが存在したら document ノードにテキストノードを作り、
     // それを個々の要素の子ノードとして追加する。
203:   return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
204:  // 引数がオブジェクトか、空文字のとき
205:  var ret = ""; // 空文字を変数 ret に代入
206:
207:  jQuery.each( text || this, function(){ // 
208:   jQuery.each( this.childNodes, function(){ // 子ノードを巡回処理
209:    if ( this.nodeType != 8 ) // 子ノードがコメントノードでない場合
210:     ret += this.nodeType != 1 ? // 子ノードが要素ノードでなければ
211:      this.nodeValue : // その値、つまりテキスト文字列を ret に代入
        // 子ノードが要素ノードの場合 this を配列の要素に入れて
212:      jQuery.fn.text( [ this ] ); //text メソッドを再帰呼び出しする。
213:   });
214:  });
215:
216:  return ret; // 文字列 ret を返す。
217: },

▼ html() メソッド
483: html: function( value ) {
484:  return value === undefined ? // value が未定義ならば
485:   (this[0] ? // 最初の配列もどきプロパティ 0 が存在すれば
      // プロパティ 0 の innerHTML 文字列内に変数 expando による
      // 「半角スペース + "jQuery" + 数字 = 数字」文字列があれば
      // それを削除してから、innerHTML 文字列を返値とする。
      // プロパティ 0 が存在しなければ null を返す。
486:    this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") :
487:    null) :
     // value が存在すれば個々の要素の子ノードを空にしてから、
     // value を子ノードとして追加する。
488:   this.empty().append( value );
489: },



・・・・・

jQuery が 1.3.1 に、そして FireFox は 3.0.6 へ

jQuery が 1.3.1 へバージョンアップ

2008/5/24 に 1.2.6 が公開されてから 8 ヶ月を経て、恒例の誕生月におけるメジャーバージョンアップ版 1.3 が 2009/1/14 に公開され、その 1 週間後の 1/21 にバグフィックスを施した 1.3.1 が公開された。

2006/8/26 に Ver 1.0、2007/1/14 に Ver 1.1、2008/1/15 に Ver 1.2.2、そして2009/1/14 に Ver 1.3───ほぼ 1 年毎に jQuery は 0.1 ずつバージョン番号を上げている。

今年もそうなるだろうと思っていたら案の定、誕生日の 1/14 に 1.3 がいわば強引にリリースされ、追いかけるようにバグフィックス版である 1.3.1 が公開されたのである。

さて、最も必要な情報は変更点である。既に多くのサイトで報じられているが、私にとって気になった変更点を列挙すると以下の通りだ。

  • CSS セレクタ Sizzle の搭載……より高速なCSSセレクタだそうだ。
  • Live イベント……類似要素へのイベント登録を代表する1つの要素にバインドするだけで済ますことが出来るようだ。
  • offset処理の高速化……これはページの表示やコンテンツ移動の高速化に大きく寄与すると思う。
  • HTMLへの要素追加の高速化

Firefox が 3.0.6 へアップ

3.0 はこれで 6 回目のマイナーバージョンアップだ。ベータ2 迄到達している 3.1 版の登場も間近だと思われるが、未対応アドオンがたくさんあると思われるので、当面は 3.0 版を使い続けることになる。

3.1 では Javascript が更に高速になり、あの chrome を越えるそうなので、早く登場して欲しいと切望している中での、暫定的対応と言えよう。

▲ToTop

jQuery Effect を使う(1)

イベント、Ajax通信と辿って来て次は?

エフェクトにチャレンジしようと思います。

Ajax 通信は非同期通信故の困難性───コード進行プロセスと同時並行で Ajax 通信が進行することに拠るスクリプトコントロール、あるいはタイミング取得の───故に、使いこなせる迄に半年を要してしまいました。

初めて Ajax 通信スクリプト作成にチャレンジしてみて、色々学習しましたが、マスターするのにこれ程の時間を要するとは、当初は予想だにしていませんでした。

本職ではない素人の、多忙な中を縫っての片手間の試行錯誤の積み重ねですから、やむを得なかったことですが、それでもなお自分に対して、要した時間長にはちょっと失望気味です。

そんな状況であるにも拘わらず、次に進もうという意欲はまだ残っているようです。

静止画像表示に多用される Effect に挑戦

そして次は...。「 easing 」なる言葉に象徴される Effect 操作のあれこれを学習しようと思います。

動きのあるコンテンツといえば、今日では DHTML でも Javascript でもなく Flash によるコンテンツ作成こそが主流であり、王道でしょうが、碌な Flash 作成ツールを持ってないため、Javascript による動的コンテンツ作成に踏み出してみようという訳です。

画像の様々な動的表示に関するコードは巷に溢れています。

文字の動的表示に至っては、初期ブラウザ(1995-2000年頃)でさえ、既にその一部の方法をHTMLタグ自身で有していました。

ですから、屋上屋を重ねて同じような動的コンテンツ表示手法を追求する意図は全くありません。今考えている動的コンテンツ表示とは、文字であれ画像であれ、それを表示する時の easing 効果をjQuery を活用して体現してみようということです。

つづく

▲ToTop



▲ToTop

・・・・・

Flash Player 9 へのバージョンアップに伴ういくつかの問題について

そのバージョンアップの影響

Flash Player 9 は調べて驚いたが何と 2 年以上前にリリースされている。「日本語版ダウンロード可となった」との記事が Gigazine に掲載されたのは 2006/6/28 だった。( 「Flash Player 9」日本語版がダウンロード可能に

しかしこの 2 年間ずっと 9 へのバージョンアップはしないまま職場及び自宅のパソコンを使い続けてきた。

今回バージョンアップしたのも積極的なものではなく、FireFox を使っていたある時、バージョンアップの知らせが表示されたので受け身にそれを受諾したのだ。

しかし、問題は直ぐに発生した。早速次の 2 つの障害が発生したのだ。

第一に、フラッシュファイルに埋め込んだクリックイベントが作動しない。

第二に、IE7 において容易にバージョンアップできない。

▲ToTop

問題解決のための格闘で今日一日を費やす羽目に...

最近は描画速度の遅い IE7 に嫌気が指しているので、専らサイト表示は FireFox で行っている。だからより深刻な問題となったのはクリックイベント無効状態であり、このブログでそれを活用しているだけに、それは早急に解決しなければならない課題となった。

さて普段 Flash ファイルを作ることはまずないし、活用できるアプリケーションがないからでもあるが、Action Script には全く手を出していない。

そんな状況下だったので、ハタと困ってしまった。何をどの様に解決すればよいのか、その方向性すら見えない状態になってしまったのである。

あれこれ検索を掛けてみても、なかなか適当なサイトが見つからず、時間ばかりが空しく過ぎ去っていく中で、やっとそれらしいサイトが見つかるまでに多大な時間を要してしまった。

しかも、Adobe 社のサイトを見てみたら、Flash Player 9 では swf ファイルに埋め込む「 href="Javascript:function()" 」が 恰も使えなくなったかのような表現が散見されたので混沌としてしまったりもした。( Adobe - デベロッパーセンター : 2008年4月のFlash Player 9のセキュリティアップデートについて

使えないならばどうしようか?、Action Script に踏み込まなければならないのだろうか?、Javascript と HTML で何とか対応出来ないのだろうか?───等々とあちこちに解決の方向性を漫然と模索してしまったのである。その全ては空しい結果に終わるとは予想もせずに・・・。

結論を言えば、「 [229684]HTML ページに埋め込まれた SWF からのリンクが機能しない場合がある 」を見て問題は解決したし、その内容たるやあっさりしたことであったものの、その解決迄に要したカオス状態に疲労感が一気に増してしまった。

こんな簡単なことをどうしてもっと分かりやすく説明したサイトがないのだろうか?、そもそもAdobe 社のサイトでなかなか目的の頁に到達できないこと自体おかしい。サポートデータベースに目的の情報はあったのだが、決してそれは検索しやすいものではないのである。

関連して付言すると、swf ファイルからのリンク切れに関するあるサイトのスレッドがありました。

それにしても約 2 年間、ページ上部に設置してあるFlash ファイルである ( このFlash Fileにもクリックイベントは埋め込んであるが、ここではそれは作動しないようにしてある。) は Flash Player 9 がインストールされている PC においては動かなかった訳で、閲覧してくださった方々には多大なご迷惑を掛けてしまいました。

▲ToTop

AllowScriptAccess パラメータについて

Adobe 社 サポートデータベース「 [229684]HTML ページに埋め込まれた SWF からのリンクが機能しない場合がある 」によれば、Flash Player 9 から導入された AllowScriptAccess パラメータは、デフォルト値が sameDomain となっているそうだ。「これがセキュリティ上、望ましい設定です。」ということらしい。

しかるに 一般に Blog サイトの場合、Domain 名はエントリイ表示モード、カテゴリイ表示モード、ファイルダウンロード先等々、決して同一ではない。このため、デフォルト値のままでは swf ファイルに埋め込んだリンクは無効になってしまう。

Blog はアメリカで始まり、当然アメリカでも普及しているはずなのに、この「無配慮」は何だろう?!「機能しない場合がある」どころではなく、「一般に機能しない」と言うべき設定値だ。

IE7 でなかなかバージョンアップが出来ない問題

こちらについては最初躓いたものの、直ぐに多くのサイトが見つかったし、Adobe 社サイトからアンインストーラを入手し、その後に普段全く使わない IE7 を起動してインストールしたので、あっさりインストール出来てしまった。

しかし、こちらについても一言言いたい。

ActiveX の面妖さ、である。何かに付け悪意のあるサイト攻撃手段として使われるため、ガードが堅くなるのは仕方ないとはいえ、いかにも使いにくい!

特に Adobe 社サイトからのダウンロードの場合、ActiveX に関する注意事項が余りにすくないため、Flash Player のダウンロード一つとってもなかなか巧くいかない場合が多かったりする訳である。───実際それ故であろう、Flash Player のアップデートが巧くいかない、Flash が動かない等々の障害情報が、Web空間に溢れているのだ。

その意味で、Flash Playerダウンロードサイトは、決してユーザーフレンドリイな仕様ではない、と言わざるを得ない。

jQuery を活用したオリジナルの Ajax 通信コードを全面的に改訂しました

何はともあれ結果を見て戴くのが最善でしょう

任意の Fc2 ブログを個別エントリイモードで開き、そこで Ajax 通信を行って、最新/以前/以後エントリイのタイトル情報などを取得する Ajax 通信プロジェクト(以下「 Fc2タイトル情報プロジェクト 」)を半年を要してやっと実現しました。その結果サンプルは以下の通りです。

サンプル 1【 Fc2総合インフォメーションブログ 】

下の画像は、Fc2総合インフォメーションブログ上で Fc2 タイトル情報プロジェクト を実行した際の表示結果抜粋です。

Ajax 通信によって最新/以前/以後エントリイのタイトル情報などを取得した結果を示すサンプル画像 1
サンプル 2【 関西ZIGZAG 】

下の画像は、人気ブログ「 関西 ZIGAZAG 」ブログ上で Fc2 タイトル情報プロジェクト を実行した際の表示結果の抜粋です。

Ajax 通信によって最新/以前/以後エントリイのタイトル情報などを取得した結果を示すサンプル画像 2
サンプル 3【 FCafe 】

下の画像は、自ブログ開設時からお世話になってきたサイト上で Fc2 タイトル情報プロジェクト を実行した際の表示結果の抜粋を示すものです。

Ajax 通信によって最新/以前/以後エントリイのタイトル情報などを取得した結果を示すサンプル画像 3

以上に例示した Fc2 タイトル情報プロジェクト の詳細については、こちらのエントリイ( ファイル置き場と Ajax 通信を活用して、任意のFc2ブログから情報を得る )をご覧いただければ幸いです。

「その光明」はなかなか見えなかった

jQuery.js を活用した Ajax 通信に、今年の 2 月からチャレンジしています。

しかし、目的とする連続的な Ajax 通信の結果取得(後に分かったことではあるが、結果取得ではなく取得結果の即刻表示)が巧くいかず、半ば諦め、4分の1ほどやけっぱちになり、4分の1ほど食らいついて解明したい、との思いが錯綜する中で、昼間の仕事の多忙さに追われて、なかなか「食らいつく」時間が取れませんでした。

しかしこの程、幸いにもその時間が取れたので、改めて jQuery.js( ver1.2.6 )コードと格闘し、最近刊行された書籍( Amazon.co.jp:『jQueryで作るAjaxアプリケーション』: 沖林 正紀著 2008年6月19日発売) )も購入し、 jQuery.js を利用した Ajax 通信に再チャレンジしてみました。

余談ですが、上記の書籍は日本語で書かれた jQuery.js 入門書としては初めてのものではないでしょうか?

その点での価値は大いに評価できると思います。

しかし、内容は、といえばまさしく「羊頭狗肉」と言わざるを得ません。

書籍名から推測すると、 jQuery.js を活用した Ajax アプリケーションのあれこれについて書かれているのではないか、と思っても決して無理はないと思います。しかし、内容は決してそのようなものではありません。

jQuery.js 入門書としては結構詳述されていますが、 Ajax アプリケーションのあれこれについてはたいした記述がありません。しかも Global イベントを利用した Ajax 通信のことしか書かれていないのです。jQuery 本家サイトを見れば分かるとおり、global イベントを利用した Ajax 通信は「それも出来る」行為であって、決して推奨されるべきものではないはずです。

何故ならば、Ajax 通信はその結果を取得するのに時間を要する行為であり、一方イベント処理も jQuery.js コード内を縦横に縦断する時間を要する行為です。二重に時間を要することを推奨すべきではないでしょう。

故に書籍名はまさに「看板倒れ」と言わざるを得ないのです。書名を「jQuery 入門」とした方が遙かに「体を表す名」だと思います。

▲ToTop

連続的な Ajax 通信による結果取得の困難性

以前のエントリイタイトル、最新のエントリイタイトル、そして以後のエントリイタイトル───Ajax 通信によるこれらの 3 つのフェースの情報取得をずっと試みる中で、特に躓いた点は過去/未来のエントリイタイトル取得、それもそれぞれに 10 個の、合計 20 個のエントリイタイトルを一発で取得する Ajax 通信でした。

もっと具体的に言えば、1回のコード進行で過去/未来の 20 個のエントリイタイトルを取得することは出来たのですが、それらの取得後可及的速やかにそれらを表示することがどうしても巧くいかなかったのです。

例えば、20個の取得を指示しても、過去エントリイは 3 つしか表示できないのに、未来エントリイは 10 個表示できたり、その逆だったり、二度目に実行すると必ず 20 個全てのタイトルを表示取得できたり、結果取得が非常に不安定だったのです。

どうしてそうなるのか、どうすれば問題を解消できるのか、その解決策を求めて上記書籍を購入したのですが、そこからは解決策は得られませんでした。

あれこれのサイトも探索しましたが、1回だけの Ajax 通信に jQuery.js を利用した例はいくつか見つかりましたが、連続してAjax通信を行って、その結果取得を試みたサイトは見つからないのです。

勿論 FireFox + Firebug を利用して進行過程をチェックしてみましたが、Ajax 通信の進行過程は流石の Firebug でもブラックボックス化してしまい、その過程を覗くことが出来ませんし、Firebug でコードのステップ進行している間にも Ajax 通信は進行するので、通信過程を垣間見ることは出来ませんでした。

更に、Firebug でコードをステップ進行するということは、その間に Ajax 通信の進行を許す訳ですから、いわば待機時間を作ることになり、通常では巧く取得できない結果も Firebug を使って進行をチェックすると取得に成功してしまうのです。

つまり、Firebug を使っても Ajax 通信のコードチェックにはならないのです。

▲ToTop

連続した Ajax 通信においてその全ての通信結果が取得できたことをどのようにして知るのか?

連続する Ajax 通信結果を可及的速やかに表示するためには、最後の通信結果が取得できたそのタイミングを Javascript に知らせなければなりません。スクリプトがそのことを知って初めて表示させる事が出来る訳ですから、通信結果が全て取得できたことをコード自体に分からせなければなりません。

その方法を色々と暗中模索した経緯の一端を以下に記しておきます。

最後の通信結果が存在すれば..

例えば「最後の通信結果が存在すれば..」という条件では不完全です。何故ならば最後の通信結果が取得できたからと言って、それ以前の通信結果が取得できている保証は何もないからです。

実際色々試してみましたが、Ajax 通信の所要時間は、それぞれの Ajax 通信の通信量と回線速度によって変化しますが、回線速度が同一であれば Ajax 通信量で決まります。つまり通信されるコンテンツの容量によって通信時間は決まります。今回の例においては、取得する HTML ファイルの容量、つまり各エントリイの本文の長短に応じて通信所要時間が変化します。大きなサイズのエントリイの場合には通信時間が長くなり、小さなサイズの場合には短くなります。

故に最後の通信が終わってもそれ以前の通信が終わった保証はどこにもありません。

通信毎に flag を立てたら..

flag を立てることも検討しましたが、Ajax 通信のどの段階で flag を記述すればよいのか、それが問題です。結果を取得できたその時に立つ flag でなくては意味がありません。ではどこに?

jQuery を活用する前提で考えてみると、なかなか適切な回答が見いだせないまま、空しく月日が流れました。

1 つの光明は jQuery.active プロパティの活用だった

flag をどの様にして立てようか思案する中で、改めて jQuery.js の Ajax 通信部分を眺め回し、また上記で紹介した書籍の Ajax 通信部分を見てみて、jQuery.js の Ajax 通信コード部分に既に flag が立てられていることを知りました。

それはクラス jQuery の 1 つのプロパティであり、コード実行時に Ajax 通信数を数えるカウンターとして利用される jQuery.active です。

そのカウンターは jQuery.ajax() メソッド内で活用されますが、その値は Ajax 通信を行おうとする度に、send コマンド実行直前に 1 だけ増加し(ver1.2.6 の 2650 行)、結果を取得し終えると 1 だけ減じられ(ver1.2.6 の 2835 行)ます。つまり、連続して Ajax 通信を指示すると、その値は順に増加し、それが終わる度に順不同で減じられる仕組みとなってます。(但し、jQuery Ajax 諸定数の1つである global 値を初期値の true ままにしておかなければ、何回 Ajax 通信を行っても、jQuery.active 値は 0 から変化しません。───jQuery.js ver1.2.6: 2650 行と 2835 行)

これを活用すれば、連続通信を開始した後にその値が 0 になるタイミングを捉えて取得結果をまとめて表示することが出来るはずだ、と推測し、実際にその推測は当たっていました。

jQuery.active プロパティを利用することにより、連続した Ajax 通信が完了したことを Javascript に知らせることの出来る flag を獲得することが出来たのです。

というよりも、正確に言えば、そもそも jQuery.active プロパティはそのような目的で作成されたもの、と言えるでしょう。

jQuery.active 利用と Ajax globalイベントの関係に係る若干のコメント

jQuery.active 利用しようとする時に、必ずしも Ajax global イベントを利用する必要はありません。global 値を true としておかなければ、jQuery.active は利用できませんが、それを利用するために Ajax global イベントを使う必要はありません。

jQuery.js の初期値のままで global 値は true となっているので、何も操作することなく「local callback」関数の中で jQuery.active を活用できます。

timerを使った jQuery.active 活用の実際

1回の Ajax 通信の場合には jQuery.active を活用するまでもなく、容易に結果を取得/表示出来ますし、複数回の通信の場合でも「概ね」問題なく結果を取得/表示できました。

これまでに試みてきた全ての自作 Ajax local コード(頁トップに掲載した関連エントリイ参照)において、jQuery.active が全てで目的通りに作動すること、つまり、第一に jQuery.active ==0 が全ての Ajax 通信が終了したことを意味し、第二に、その値になれば全ての Ajax 通信結果が取得出来ていることを確認しました。

jQuery.active 活用の要点

Ajax 通信コード部分より下に、以下のように同一関数の間欠起動を設定し jQuery.active 値がゼロになる迄 Ajax 通信完了を待機し続け、ゼロになってから(つまり全ての Ajax 通信が終わってから)結果をまとめて表示すれば良い訳です。

 function display(){
  if (jQuery.active==0) {
   if (timer) {clearInterval(timer);timer=null;}
   ・・・ここに全ての通信結果を表示するためのコードを記述する。・・・
  }
 }
 var timer = setInterval(display,milsec);
しかし、1 回の jQuery.active 活用だけではまだ不十分だった

ところが、1 回の jQuery.active の活用だけではどうしても全ての Ajax 通信結果を一括表示出来ないケースが発生しました。つまり、一括取得は出来ているのに、一括表示が出来ない場合が生じたのです。

そのケースとは、4 つめのチャレンジとして試みた、任意の Fc2 ブログの任意のエントリイの以前/以後/最新エントリイタイトルを取得するコードの場合でした。当然、そのような現象が発生する理由について、一人悶々とと分析しました。分析し続けました。数ヶ月に亘って・・・。

そしてやっと到達した要点は次のような、今更ながらの基本的なことでした。

  • 第一に、( 分かってみれば余りに当然のことですが )連続する Ajax 通信中において、先行する通信コードの中で設定した変数を、後続する他の通信中で活用しようとする時、先行する通信の中で変数が取得できていない時点で、後続する通信コードの中でそれを利用しようとすれば、当然エラー( 値は undefined となる)となること
  • 第二に、最も確実な連続 Ajax 通信コードの書き方は、先行する通信が終わったことを確認する何らかの flag を設定し、それを確認してから次の Ajax 通信が進行するようにコードを記述すればよいこと
  • 第三に、しなしながらその方法を採用すると、全体の通信完了までに相当の長時間を要してしまい、実用的ではないこと
  • 第四に、従って jQuery.active を複数回活用して各 Ajax 通信完了 を待機させれば、確実に各通信完了を javascript に認識させられること

───などでした。

そこで原点に返って可能な限り Ajax メソッド内で行ってみた

上記の再確認した基本的事項を踏まえて、Ajax 通信コードの外であれこれ操作して Ajax 通信結果を取得する試みがどうしても巧くいかないことから、原点に返って全てを Ajax 通信メソッドの内で行ってしまった方がよいのではないか、と考え直しそれを実行してみました。

すると驚いたことに、あるいは当然の結果なのかも知れませんが、これまでの悩みが嘘のようにあっさり解決してしまったのです。

詳細は以下のエントリイに記述しました。

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

201101130146
201012082037
201011250125
201011220020
201011120654
201008080834
201007272344
201007150038
201007090028
201002150132
200910082246
200908161701
200907201110
200905010758
200904050150
200903300116
200902090114
200807260201
200807192329
200807050116
FC2 Management