05 | 2017/06 |  07

  1. 無料サーバー

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

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


スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

投稿が忘れた頃に必要になった

これから長々と書きたいが、休憩が終わったので、帰宅してから、先を綴る。
スポンサーサイト

Kindle Fire に興味津々

 現在、Book Reader どころかタブレット端末も所持していないが、7インチサイズのタブレット端末が続々と登場した昨今、愈々Book Reader として、あるいは新聞購読用として、タブレット端末を購入するかどうか、検討を開始した。

またしても一ヶ月制限に掛かってしまった

だからこうして意味のない記事を書いている

jQuery参考書の決定版が刊行された(8月)

ついにあの O'Reilly 社から jQuery 書籍が刊行された

『jQueryクックブック』───それは待望した書籍だ。

年々静かに普及の波が広がり、あの Microsoft 社のサイトさえ使っているらしい jQuery。なればこそ、当然 O'Reilly 社が目を付けないはずがない。必ず出版されるだろう、と踏んでいたが、ついに刊行されたのである。jQuery 解説本の決定版が!

クックブックは O'Reilly 社特有の呼称で、同名のタイトルが付いた書籍が多く出版されている。「 料理本 」は O'Reilly 社の 1 つの「看板」書籍だ。そのクックブックが刊行されたということは、紛れもなく jQuery が広く普及したことの証左に他ならない。

内容は・・・素晴らしい!

約 460 頁で 3,780 円。例に漏れず O'Reilly 社の書籍は高価だ。

しかし、それだけの価値は十分にある───但し jQuery 愛好者に取ってのみ・・・。

Javascript 初心者でも、jQuery を知らなくても良い───かのような巻頭文があるが、それは正しくない。かなり高度な内容も一部に含まれており、両者にそれなりに通じていなければ、とても読み通せるものではない。

しかし、裏返せば内容は相当濃い。

概ね全体に目を通し終えたが、既に知っていることも多いが同時に知らないことも多く、貴重な知識を多く得ることが出来た。

jQuery 愛好者あるいは利用者にはきっと必読書となるだろう。

信じ難い 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 が文としての厳格な解釈を適用しないようにするのが得策だ。

目次の仕様を変更───必要に応じて簡単なマウス操作で見え隠しする

新しい目次作成+頁内ジャンプ プラグインの設計

結論を言えば、上の関連エントリイリストに記した 2 つのプラグインの統合を行いました。

No.782 の目次自動作成プラグインは、ページトップの近くに、常時固定的に表示する目次を自動的に作成するものであり、No.793 は頁内移動を容易に行うためのものです。

これらを暫く使ってみた結果、統合して 1 つのプラグインにまとめた方が使い勝手がよいことに気づきました。

目次として自動登録するのは h4 タグです。そして、頁内ジャンプは主に目次項目を使用するものでした。つまり共通の対象を操作するわけです。ならば統一できるし、その方が効果的に使えると考えたのです。

また、頁内ジャンププラグインは敢えて animatedPopup プラグインを利用していますが、ここで目的とする機能は重いプラグインを使わなくても、もっと軽快に達成できるのではないか、否、達成すべきだとも考えました。

こうして 2 つのプラグインを統合した軽快な目次作成+頁内ジャンププラグインを完成させました。

仕様

以下のように、必要に応じて目次を表示させ、また不必要になったら消えるようにしました。目次内の項目をクリックすると当該箇所にジャンプすることは言うまでもありません。

  1. 目次は固定的には表示させず、必要に応じて表示/隠蔽するようにしました。
  2. エントリイ頁が開かれた時には、画面上部の中央部分に目次が表示されます。その時目次を使いたくなければ、目次ボックスの文字以外の箇所をクリックするか、あるいは window をスクロールすれば目次を消すことが出来ます。
  3. 一旦消えた目次を見たくなった場合には、マウスカーソルをその時々の window 左辺付近に近づけると、その時のマウスカーソルの位置に応じて、目次が右にスライドしながら表示されます。つまり、スクロールの有無や多寡に関わらず、時々の window 左辺にマウスカーソルを近づけると、window 左辺に接して目次を表示するようにしました。この「 浮動的に 」表示/隠蔽する目次がこのプラグインの最大の特徴です。
    なお、ジャンプしたくない場合には、既に触れたように一寸頁をスクロールさせれば目次を隠蔽出来ます。
  4. 目次内の任意の文字列をクリックすると、当該箇所にジャンプします(スクロールします)。その際には、既述のように、LI タグに登録したイベントハンドラーによって目次は隠蔽されます。
  5. このプラグインは起動元を特定する必要がありません。$() を起動元にしても起動できますし、起動元は何であっても構いません。なお、背景色を変えて起動したい場合には、$().makeTableContents("specified color") と指定すれば良いだけです。

▲ToTop

コード

■ slideSide

上下方向の slideToggle , slideUp , slideDown インスタンスメソッドに準じて、横方向のスライドメソッド slideSideToggle , slideSideOut , slideSideIn を以下のように作りました。duration、easing、complete callback 関数 の初期値も指定していますので、引数を指定せずに起動すれば、継続時間は slow を、easing 関数は swing を、アニメーション終了後に起動する関数は無指定を、それぞれ指定することになります。

この横方向スライドメソッドは、縦方向の Up/Down メソッドと共に、かなり使い回すことの出来る汎用的なものとなるでしょう。

$.fn.slideSideToggle = function(duration,easing,callback){
  this.animate({width:"toggle", marginLeft:"toggle", marginRight:"toggle",
    paddingLeft:"toggle", paddingRight:"toggle"},
    duration || "slow", easing || "swing", callback || function(){});
}
$.fn.slideSideOut = function(duration,easing,callback){
  this.animate({width:"hide", marginLeft:"hide", marginRight:"hide",
    paddingLeft:"hide", paddingRight:"hide"},
    duration || "slow",easing || "swing", callback || function(){});
};
$.fn.slideSideIn = function(duration,easing,callback){
  this.animate({width:"show", marginLeft:"show", marginRight:"show",
    paddingLeft:"show", paddingRight:"show"},
    duration || "slow",easing || "swing", callback || function(){});
};
■ makeTableContents
 1:$.fn.makeTableContents = function(color){
 2:/* $.fn.makeTableContents(color)メソッド
 3: * color:目次の背景色を指定すれば、default:"navy"を変更可能
 4: * 起動方法:$().makeTableContents("specified color") or $().makeTableContents()
 5: * release:2010/12/4
 6: */
     // エントリイモードではない時には何もしない。
 7:  if (!/.+blog-entry.+html/.test(location.href)){
 8:    (function(){return false;})();
 9:  } else {
10:    $(function(){
         // entry_body に h4 タグがなければ何もしない。
11:      if (!$("h4","div.entry_body").length) {return (function(){return false;})();}
         // もし既に目次がある場合にはこれを削除する。
12:      if ($("#tablecontents").size()) {$("#tablecontents").remove()};
         // ローカル変数定義
13:      var $contents, bgColor = color && typeof color==="string" && color|| "navy",
14:          initScrollTop = $(window).scrollTop();
         // 目次タグ要素の作成
15:      $contents = $("<ol id='tablecontents' title='目次が消えた後でも、
           マウスカーソルを window 左辺に近づけると再度表示させることが出来ます。
           <br />また、window をスクロールすると目次を隠蔽することが出来ます。' />")
16:        .css($.extend({}, // 初期値(opts) と引数を統合
17:          $.fn.makeTableContents.opts, bgColor!==null ? {background:bgColor}:{})
18:        .prependTo($("body"));
         // entry_body 内の h4 毎に目次項目を作るイテレート処理を行う。
19:      $("h4","div.entry_body").each(function(i){
20:        $(this).wrapInner("<a id='tablecontents"+i +"'></a>");
21:        $contents.append("<li><a href='#tablecontents"+i+"'>"+$(this).text() +"</a></li>");
22:      });
23:      // 目次の幅は 400 px またはそれ以下とする。
24:      var cW = Math.min($contents.width(),400),
25:          cH = $contents.css({width:cW}).height(), // 目次の高さ
26:          oW = $contents.css({width:cW}).outerWidth(), // 目次の幅外寸
27:          direction = true; // direction : true -> up down, false -> side
         // 上下方向/横方向の方向別の 3 つの slide メソッドの選択指定
28:      var slideFuncToggle = function(){
29:        return direction ? this.slideToggle("slow") : this.slideSideToggle();
30:      };
31:      var slideFuncOut = function(){
32:       return direction ? this.slideUp("slow") : this.slideSideOut();
33:      };
         // このプラグインでは使用しないが、一応定義しておく
34:      var slideFuncIn = function(){
35:        return direction ? this.slideDown("slow") : this.slideSideIn();
36:      };
37:      // 目次(=ジャンプリスト)表示関数
38:      var displayJumpList = function(top){
39:        cPopCSS = !top ? { // top 値がゼロの時
40:          top : $(window).scrollTop()+160,
41:          left : (($(window).width()-oW)/2),
42:          height : cH,
43:          overflow:"auto"
44:        } : { // top 値がゼロではない時
45:          top : top,
46:          left :$(window).scrollLeft(), // 画面左端に配置
47:          height : cH, // 高さを変化させないために
48:          overflow:"auto"
49:        };
           // css 値を設定してから slide メソッドを呼び出す。
           // 高さを指定して横方向アニメが起きた時に高さを変化させないようにする。
           
50:        slideFuncToggle.call($contents.css(cPopCSS));
51:      };
         // 目次各項目への click イベントハンドラーの登録
52:      $("li",$contents).each(function(){
53:        $(this).click(function(){
54:          slideFuncToggle.call($contents); // 目次を隠蔽する
55:        })
56:      });
57:      displayJumpList(); // 頁オープン時に目次を表示する
58:      $("body").mousemove(function(e){ // mouseが動いた時
           // マウスカーソル X 位置が window 左辺から 10px 以内にあって、
           // かつ 目次が表示されていなければ
59:        if (e.pageX-$(window).scrollLeft()<10 && $contents.is(":hidden"){
60:          direction = false; // 横方向を指定し
61:          displayJumpList(e.pageY); // 目次を top:マウスカーソル Y 座標に表示する
62:        }
         // window scroll イベントハンドラーの登録(目次の隠蔽)
63:      })
64:      $(window).scroll(function(){
           // 目次が表示されていて初期スクロール Top 値と
           // 今行ったスクロール後の Top 値が 20 px 以上差がある場合には
65:        if (Math.abs(initScrollTop-$(this).scrollTop())>20 && !$contents.is(":hidden")){
66:          slideFuncOut.call($contents); //目次を隠蔽する
67:          initScrollTop = $(this).scrollTop()// 現在値を初期値とする。
68:        }
69:      });
37     });
70:  return this;
71:  }
72:};
73:// 目次の CSS 初期値設定
74:$.fn.makeTableContents.opts = {
75:  position:"absolute",
76:  zIndex:"1010",margin:0,padding:"0.5em 0.5em 0.5em 2em",
77:  border:"2px ridge white",textAlign:"left",lineHeight:"1.1em",
78:  background:"navy",display:"none"
79:}

ブログ頁内ジャンプ移動に際して、ジャンプ先からジャンプ元に直ぐに戻れるようにする 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"}});
}); 

ID 名を持つ要素の clone 要素を複数作成する jQuery プラグイン

jQuery clone インスタンスを作成するプラグイン

このプラグインは、その名の通り、jQuery clone インスタンスを作成するものです。

ポップアップ要素を複数同時に表示させたい場合などに使用することを想定しています。

以下、アンダーライン部は補足説明ポップアップが設定してあります。

起動式は jQuery("#id").makeClone(n,target-jQuery-Instance,AfterOrBefore) で、これにより ID 名として idName を持つ要素の clone を n 個作成し、target jQuery インスタンスの後ろまたは前にまとめて配置します。

必要に応じてどの要素の前後にも clone を配置出来ます。但し、或る要素の中に配置する方法はここでは採用しませんでした。別途検討したいと思います。───願わくば、前後配置内包配置包含配置の全ての配置が可能なプラグインに出来ればと夢想しています。

特徴

  1. clone 元要素は DOM ツリーのどこにあっても構いませんが、当然存在していなければなりません(苦笑)。
  2. 一旦任意の数の clone 要素を作成した後に、異なる個数の clone を、異なる要素の前後に配置することも可能です。その場合以前に作成した clone 要素は全て削除され、改めて後で指定した数の clone 要素が、新しい対象要素の前後に配置されます。
  3. 任意の要素の前後に、任意の数だけ clone 要素を配置出来ますが、同じ ID 名の要素を元とする clone 要素を、異なる要素の前後に配置する機能はありません。
  4. 作成した clone 要素はその定義から元要素の属性を全て引き継ぎます。例えば、元要素が display:'none' となっていれば、作成された clone 要素も一切描画されませんし、絶対配置されている ID 名の要素の clone は絶対配置となります。しかし、このプラグインの返値を clones jQery インスタンスにしたので、作成後に jQuery メソッドチェーンを利用して、自由に属性を変更出来ます。
  5. clone 要素の ID 名は元要素の ID 名に番号を付加したものとしました。(例)元要素の ID 名:"animPopup" → clone 要素の ID 名:"animPopup1"、"animPopup2"、・・・
  6. clone 元要素に子要素があって(孫要素は対象外)、それにも ID 名が存在した場合、clone 要素の子要素の ID 名も、子要素の ID 名 +番号 となるようにしました。
  7. clone 要素を第二引数の前または後の、いずれに配置するかを第三引数で指定します。無指定の場合には後ろに配置します。
  8. 前又は後の配置対象となる要素には ID が振られている必要はありません。任意のしかし1つの要素だけを指定出来ます。

コード

  1:(function($){
  2:$.fn.makeClone = function(n,$target,AfterOrBefore){
  3:/* ID 名として idName を持つ要素の clone を n 個作るメソッド
  4: * 当該メソッドの起動元インスタンスは clone 元要素で ID 名付きで指定する。
  5: * 一旦 clone を作成後に、n を増やしたい場合にも、重複作成はしないよう
  6: * コーディングしてあるので、単純に n を指定すればよい。
  7: * なお、作成した clone 要素をどこに挿入するかは別問題であり、挿入用の
  8: * コーディングが別途必要となる。
  9: * release:2010/11/14
 10: */
 11:	var errFunc=function(al){al!==undefined && alert(al);return false;};
 12:	if (n===undefined) errFunc("引数が指定されていません。\n作成する clone の数を
            正の整数で指定してください。\n0 を指定すると作成済みの clone を削除します。");
 13:	if ($target && !$target.jquery) errFunc("第二引数は clone を配置する前、
            または後ろの jQuery インスタンスを指定してください。");
 14:	if (AfterOrBefore && typeof AfterOrBefore!=="boolean")
          errFunc("第三引数は、trueか fault を指定してください。\ntrue は第二引数の
          後ろに、false は前に clone を配置することを意味します。\n指定しない場合には
          true 扱いとなり後ろに配置するよう設計しました。");
 15:    if (n && (!$target || $target.length===0 || $target.length >1))
          errFunc("第二引数が適切ではありません。指定し直してください。\n
          複数の要素を指し示す指定は出来ませんし、n がゼロの時以外は無指定も
          出来ません。");
 16:
 17:	// 起動元インスタンスは必ず $("#id") 形式とする
 18:	var clones=[], $tmp, i=1, k=1, id = this.attr("id");
 19:	if (!$("#"+id).length)
 20:		return errFunc("その ID 名の要素は存在しません。");
 21:	var setClone = function(){
 22:		for (;$tmp=$("#"+id +i),$tmp.size();i++) $tmp.remove();
 23:		for (;k<n+1;k++){
 24:			clones[k-1] = $("#"+id).clone().attr("id",id +k);
 25:			var $c = clones[k-1].children(":[id]");
 26:			if ($c.size()){
 27:				$c.each(function(){
 28:					$(this).attr("id", $(this).attr("id")+k);
 29:				});
 30:			}
 31:		}
 32:		return $(clones);
 33:	}
 34:	return (AfterOrBefore===undefined || AfterOrBefore) ?
          setClone().insertAfter($target) : setClone().insertBefore($target);
 35:};
 36:})(jQuery);

サンプル

頁内に存在する ID 名「popup」要素の clone を、下のボタン直下に 5 つ作る
頁内に存在する ID 名「popup」要素の clone を、この直下、ボタンの上に 3 つ作る

これらのサンプルでは、結果を見せるために元要素の CSS 設定をいくつか変更しました。例えば、position は "relative" に、display は "block" に変えています。幅も自動的に width:100% となっています。

作成苦労談

作 成 中

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

恐縮ですが 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);

職場のWebサイトにNAMAZUを導入───6年越しの念願が叶う

2005年1月に開設した職場のWebサイト

それはこの Fc2 無料ブログサービスが始まって間もない時期だった。

現在の職場に、外部からアクセスできない Web サイトを HTML、CSS、Javascript により全くオリジナルの形式で開設したのは、2005 年 1 月だった。

それは専ら仕事に関わる情報だけを取扱い、常時業務情報にアクセスしやすいように心掛けたラン内で閉じたサイトで、業務情報へのショートカット、パソコンの使い方・ソフトやデバイスドライバのインストール解説・Office ソフトの使い勝手向上等々の Web サイトリンク集等々を寄せ集めた、小さな小さな Web サイトである。

それでもリンク情報は日に日に増大し、アクセスできる情報(職場内の各種システムへのショートカット、基礎資料、業務関連 WWW サイトへのリンク集など)は、6年の星霜を重ねて優に万の単位に累積し、「常時必要な情報に直ぐにアクセス出来る」機能性は、アクセス情報が増えるにつれ、年々徐々に低下していた。

トップ頁に掲載している文字情報だけでは、それが分類されていても探す手間が大変になってきていた。

だから当然、サイト内検索機能を搭載する必要性が年々高まっていた。

Web サーバーソフトは 04Web サーバー

開設に当たって Web サーバーソフトの選択を迫られたわけだが、AN HTTP や Apache ではなく 04Web サーバーを選んだのは、偏に導入が簡単に思えたからに他ならない。

実際、極めて容易に開設に漕ぎ着けたのだが、そのこととは裏腹に、NAMAZU との相性についての情報は少なく、かつ NAMAZU 自体の情報も素人が容易に理解出来るサイトは決して豊富にある訳ではなく、NAMAZU 導入の敷居は非常に高かった。

実際過去に数度、NAMAZU 導入にチャレンジしたことはあるが、その都度 NAMAZU インストールで躓いてしまい、成功しなかった苦い経験もした。

だから、半ば諦めかけ、日々の業務に負われる中で次第に NAMAZU から遠ざかってしまっていた。

かくして気づけば 6 年の歳月が流れ去った。

▲ToTop

ついに NAMAZU を導入

今回、性懲りもなく NAMAZU 導入にチャレンジしたのは、サイト内検索の必要性がますます高まる中で、その機能がどうしても必要であるとの思いを新たにし、かつ、いくつかの Web サイトを探索してみて、04Web サーバーでも問題なく NAMAZU が作動すると確信を得たからである。

そして、今導入出来なければこの先もずっと不可能であると判断したからである。

実際、NAMAZU のインストールは何度やってもなかなか成功せず、PPM インストールの段階で何度も何度も躓き、今回もまたしても不成功か?!───と諦めかけていた。

そんな折に、偶々検索してヒットしたサイトから PPM に関連するプログラムのダウンロードを試みて、手動で PPM ファイルのインストールを試みたところ、成功してしまったのである!

その、躓きつつも成功した NAMAZU 導入の苦労談は別途まとめることにしたい。

jQuery.width()/height() メソッドの挙動を改めて解読する

問題の単純化のために、まず jQuery(・・・).width() メソッドに着目して分析を進め、一通りの作業を終えた後に、jQuery(・・・).height() メソッドにも言及することにします。width メソッドに付いて言えることは、本質的にそのまま height メソッドにも妥当するからです。

位置指定要素と通常配置要素の表示描画状態における幅

下の例でブロックボックス要素の描画幅について比較してみました。

このボックスは、まず position 属性を順に、無指定、relative、absolute、absolute と指定した 4 つの div 要素( 全て横幅は無指定で、class 名を target786 としました)を作り、それらの中にそれぞれ文字列を配した上で、それを包含する div 要素を offsetParent 要素として作成し配置したものです。最も外側の offsetParent たることを期待されている div 要素には、そのために position : relative が指定されています。ここに position:relative 指定した要素の top 値と left 値は共にゼロとしています。

なお、.target786 DIV 要素の offsetParent のコンテンツ領域を明示するために、このコンテンツ領域を padding、border 及び margin 値を全てゼロとした div 要素で囲み、pink 背景色を指定しました。

更になお、.target786 DIV 要素の margin 域を明示するために、.target786 DIV 要素を包含する padding、border 及び margin 値を全てゼロとした div 要素を配置し、その背景色を茶色としました。

この pink の背景領域は以下の要素を包含する要素のコンテンツ領域である。
これは通常配置されたdiv要素内に配置された文字列である。
これは相対配置されたdiv要素内に配置された文字列。
これは絶対配置されたdiv要素内に配置された文字列である。
これも絶対配置div要素内に配置された文字列

上の例で直ぐ分かるように、静的配置及び相対配置要素の横幅に注目すると、中のコンテンツサイズ(この場合文字列)に影響されず、margin、border、padding 及び内容が offsetParent 要素のコンテンツ領域横幅一杯に展開されています。(※ target786 DIV の上下の margin は相殺されている。)

一方、絶対配置要素の場合の横幅はそのコンテンツ幅に左右され、かつ margin 値を含まない固有の値となっています。(固定配置の場合にも同様に当該要素のコンテンツ幅によって決まる固有の値となりますが、固定配置の例はここでの例示には馴染まないので省略しました。なお、固定配置の場合にはその包含関係に関わらず offsetParent は body 要素となります。)

以上の position 指定による表示幅の違いから、或る要素のコンテンツの横幅を知ろうとすれば、そのコンテンツを絶対配置すれば計測できることが分かります。

そして、一般に要素は margin、border 及び padding の各値を有していますので、要素の幅にはコンテンツ幅、padding 辺幅、border 辺幅及び margin 辺幅があり、これらの計測のために次の 4つの jQuery インスタンスメソッドが定義されています。それは jQuery(・・・).width()、jQuery(・・・).innerWidth()、jQuery(・・・).outerWidth()、jQuery(・・・).outerWidth(true) です。

さて、絶対配置要素の幅がそれを包含する offsetParent サイズに依存せず、当該要素固有の値になることを利用して、当該要素の算出スタイル値を算出することが出来ます。jquery.js では swap メソッドがそれです。次に、swap メソッドを使う jQuery(・・・).width() メソッドの実行過程を具に追いかけ、コンテンツ幅を取得する方法を跡付けてみます。

▲ToTop

jQuery(・・・).width() メソッドはどのようにして対象要素のコンテンツ幅を測るのか?

以下のような複雑な過程を経て、対象要素 elem の算出スタイル width 値が算出されます。

  1. width インスタンスメソッドの定義
    width インスタンスメソッドは jquery.js ver1.42 の 6019~6074 行で定義されていますが、window や body を除く一般の要素の横幅計測は 6067 行の jQuery.css(elem,type) メソッド呼び出しを通して行われます。( jQuery().width() メソッドの場合には type 値は "width" となります。)
  2. display 属性値が none でない時
    1. 呼び出された jQuery,css クラスメソッドでは、elem.offsetWidth がゼロでなければ、つまり display 属性値が none でなければ getWH() 関数を実行し、この関数の中から、jQuery.curCSS クラスメソッドを呼び出します。
    2. この jQuery.curCSS クラスメソッドを呼び出し時には、第 3 引数が true に指定されるので(#4475~4481)、style 属性の有無に拘わらず、強制的に elem 要素の算出スタイル width 値が計測されます。なお、上で見たことから容易に分かるようにこの算出スタイル width 値は、当該要素が静的又は相対配置されている場合には offsetParent のコンテンツ幅となり、絶対又は固定配置されていれば、当該要素のコンテンツ幅となります。
  3. display 属性値が none の時
    1. 他方、elem.offsetWidth がゼロの場合(つまり、display 属性が none の時)には、jQuery,css クラスメソッドは jQuery.swap クラスメソッドを呼び出します。
    2. swap メソッドは style 属性の一部を一時的・強制的に入れ替えて算出スタイルを算出する関数で、position 値を absolute に、visibility 値を hidden に、display 値を block にそれぞれ入れ替えてから、getWH() 関数を呼び出して算出スタイル width 値を算出し、その直後に一時的に入れ替えた style 属性値を元に戻します。
      ここで注目すべきことは、絶対配置指定を行い、display を block に指定してブラウザに当該要素を描画させつつ、可視属性を hidden としていることです。これによりブラウザ上の当該要素の表示状態を全く変化させることなく、つまり表示させていない状態(display:none)を実質的に変化させることなく、算出スタイル値を測定しています。
      こうして swap メソッドと getWH 関数の巧みな組み合わせによって、要素のコンテンツ幅計測が行われていることが分かります。
【結論】
  1. 測定対象要素の position 指定値とその表示有無( display属性値 )によって、jQuery(・・・).width メソッドで測られる幅の値は異なります。
  2. 対象要素を表示状態( display:none 以外 )にして width インスタンスメソッドを適用すると、対象要素を包含するブロック要素のコンテンツ横幅から、当該要素の左右の padding 幅、当該要素の左右のボーダー幅及び左右のマージン値の 3 つの合算値を差し引いた値が計測されます。この値は必ずしも当該要素のコンテンツそのものの幅と同一ではありません。算出される値は当該要素がボックスとして取り得る最大のコンテンツ幅となります。
  3. 対象要素を非表示(display:none)にして width インスタンスメソッドを適用すると、当該要素の左右のパディング幅や左右のボーダー幅及び左右のマージン幅は無視され、対象要素のコンテンツ横幅が計測されます。

画像のようなインラインブロック要素を包含するブロック要素に jQuery(・・・).width() メソッドを適用して得られる値は何を指すのか?

これまで、要素を非表示状態にして width インスタンスメソッドを適用すれば、当該要素のコンテンツ幅が測られることを見てきました。

では、コンテンツがインラインブロック要素の場合で、その包含ブロック要素を非表示状態にして width メソッドを適用した場合、計測される値は何を指すのでしょうか?

img HTML タグには width や height だけではなく、W3C 非推奨ですが border、vspace、hspace 属性が設定出来ます。また span 要素でインラインブロック要素を包含して margin を設定することさえ可能です。そして実際多くの Web サイトでこれらの非推奨属性が未だに多用されています。このようなインラインブロックを包含するブロック要素に width メソッドを適用した場合の値について調べます。

(このように恰もブロック要素のように余白などが設定出来ることが、まさにインラインブロックと呼ばれる所以です。)

結論は自明です。インラインブロック要素の場合、余白やボーダー迄を含めてコンテンツなのですから、それは画像の幅+画像の左右の padding 幅+画像の左右のボーダー幅+画像の左右の margin 幅の合計値となります。なお、注意しなければならないことは、ここで言う padding、border、margin はインラインコンテンツを包含するブロック要素のそれらではありません。画像に固有に設定されている padding 等を指します。

以上のことを以下の例示で検証します。

この下の画像は id 名 img1-786 の div 要素の中に配置しましたが、インライン要素である画像タグには width="450px" 指定の他に、vspace="5px"、hspace="10px"、border="4px"等を指定してあります。

この div 要素に width() jQueryインスタンスメソッドを適用して「コンテンツ幅」計測を行ってみます。ここでは対象とする div 要素が表示されているため一寸工夫が必要です。具体的には div 要素の clone ノード( CNと呼ぶ )を作り、その display 値を none とした上で jQuery(CN).width メソッドを適用するのです。

その結果は、画像の直下にリアルタイムで表示するように script を組み込みました。

さて、div 要素に width メソッドを適用して得られた値 478px は、画像の幅を超えています。これは容易に検証できますが、width:450 + hspace:10*2 + border:4*2 に他なりません。つまり、インラインブロック要素自身の幅とボーダー幅とパディング幅の合計値となっています。

なお、ここでも IE だけは正しく作動しませんでした。Firefox、Chrome、Opera、Safari (全て Windows 版)では全て 478px となりましたが、IE8 の場合 478px ではなく 458px となってしまいます。余白幅が加算されないのです。その理由と IE7 や IE6 の場合どうなるかは検証に値する問題でもないでしょう。無視します。

こうして、インラインブロック要素を包含するブロック要素に jQuery(・・・).width メソッドを適用すると、インラインブロック要素のコンテンツ幅+余白+ボーダー幅の合算値が取得できること、並びに IE の場合には hspace を無視するバグがあることが検証できました。

jQuery(・・・).height() メソッドについて

これまで width メソッドについて述べてきたことはそのまま、height メソッドにも妥当するはずです。何故ならばこれら二つのメソッドは全く同一の jquery.js コードを利用するからです。

そのことを明らかにするために上の写真の、今度は高さを測って実証してみます。

既にコンテンツサイズ計測用の clone ノードは display:"none" 状態で作成済みなのでこれを再利用します。

リアルタイムに script で計測し、計測結果がこの下に表示されますが、画像そのものの高さが 301px であるのに対して display:"none" 状態の clone ノードに対して height メソッドを適用した結果は、予想通り 319px(301+vspace 5*2 + border 4*2。但し IE では vspace 値を拾わないので結果は 309 pxとなる。)となっています。

また、width メソッドで言及した結論は、 height メソッドについてもほぼ同様に以下のように主張出来ます。

【結論】
  1. 測定対象要素の position 指定値とその表示有無によって、jQuery(・・・).height メソッドで測られる高さの値は異なります。
  2. 対象要素を表示状態にして height インスタンスメソッドを適用すると、対象要素の offsetParent のコンテンツ高さから、当該要素の上下のパディング幅と上下のボーダー幅を差し引いた値を計測します。この値は基本的に当該要素のコンテンツ高さに等しくなります。
  3. 対象要素を非表示(display:none)にして height インスタンスメソッドを適用すると、対象要素のコンテンツ高さを計測します。
  4. width メソッドにおいては、display 値が block と none の場合とでは結果が異なることがあるのに対して、height メソッドでは、display 値に左右されずに同一値となります。

jquery.js を使ってインラインコンテンツサイズを計測する

jquery.js における要素サイズ計測

HTML 要素には、一般には特定の CSS スタイルシートが適用されていますし、各要素には style 属性が設定されている場合もあれば、されていない場合もあります。style 属性が設定されている場合であっても、要素サイズに係る CSS スタイル属性の、一部又は全部が指定されていない場合もあります。

このような状況に対して、jquery.js では CSS style について統一的な対応が図られており、例えば要素サイズを計測する場合、jQuery(・・・).width、jQuery(・・・).innerWidth、jQuery(・・・).outerWidth、jQuery(・・・).outerWidth(true) 各メソッドを使って、内容辺幅、パッディング辺幅、ボーダー辺幅及びマージン辺幅を取得出来ます。(jQuery(・・・).width メソッドだけは設定も可能)。そして同様なメソッドが高さについても存在しています。

そして、これらの要素サイズ計測メソッドは、全て 2 つのクラスメソッド( jQuery.css と jQuery.curCSS )を使用します。jQuery(・・・).css メソッドはサイズの設定/取得が可能で、jQuery(・・・).css メソッドから必要に応じて jQuery(・・・).curCSS メソッドが呼び出されます。

さて、これらの 2 つのクラスメソッドは、特別な指定をしない限り、スタイル属性が設定されていればその値を使用し、設定されていない場合には、算出スタイル値(computed style value。IE で言うところのカレントスタイル値)を計測します。jQuery(element).css(propertyName) メソッドはそのように作動します。(勿論、style 属性による指定を無視して、算出スタイル値を計測することも可能ですが、ここでの論議の焦点からずれるので、このことについては触れません。)

以上についてはこちらのエントリイで詳述しました。

では、jquery.js を使って、ブロックボックスサイズではなく、そのコンテンツサイズ、特にインラインコンテンツサイズを知るにはどうすればよいのでしょうか?( ※ コンテンツがブロック要素の場合には上述のメソッドで容易に取得可能 )

例えば、ul 要素内に複数の li 要素があるとします。問題を簡単にするために li 要素はデフォルトのブロック要素であると仮定します。このとき、各 li 要素のコンテンツは文字列だけの場合もあれば、画像や動画の場合もあります。それらの混合である場合もあるでしょう。そしてそれぞれのコンテンツの幅や高さは一般にコンテンツ毎に異なります。

このようなケースにおいて、インラインコンテンツの幅と高さを、jquery.js を使ってどのように取得すれば良いのでしょうか? ブロック要素ではなくその中のインラインコンテンツサイズを jquery.js を使って取得すること、これがこのエントリイのテーマです。なお、その際にはブラウザの表示内容や状態を、見た目では一切変えることなく計測することが前提となります。

▲ToTop

表示/非表示描画と可視/隠蔽描画

CSS 属性には要素の描画に関して、非常に似通っているが異なる内容の属性があります。それは display 属性と visibility 属性です。

前者は直訳すれば表示属性であり後者は可視属性と呼べるでしょう。以下この訳語を使います。

表示属性はよく知られている block、none、inline の他に、list-item、marker、compact、run-in、table、inline-table、table-caption 等々、多数の値が定義されています。他方可視属性は、visible、hidden、collapse の 3 つの値しか定義されていません。

さて、jquery.js を活用し、これらの 2 つの属性を組み合わせることで、出来るだけ効率的にインラインコンテンツのサイズを計測する方法について、以下に例示を示しつつ一般化してみます。

▲ToTop

jQuery(・・・).width()/height() メソッドで測る

このエントリイ上部にある「目次」を利用して、ol や li などのブロック要素とその中に配置されている文字列インラインコンテンツのサイズ計測を行いました。その結果は以下の通りです。

// ol ブロック要素の測定
 1: $("#contents").css({display:"block",visibility:"visible"}).width() //604
 2: $("#contents").css({display:"block",visibility:"hidden"}).width() //604
 3: $("#contents").css({display:"none",visibility:"visible"}).width() //354
 4: $("#contents").css({display:"none",visibility:"hidden"}).width() //354
// li ブロック要素の測定
 5: $("#contents>li:first").css({display:"block"}).width() //604
 6: $("#contents>li:first").css({display:"none"}).width() //241
 7: $("#contents>li:eq(1)").css({display:"none"}).width() //247
 8: $("#contents>li:last").css({display:"none"}).width() //354
// インラインコンテンツそのものの測定
// 9から11 は $("#contents>li").css({display:"block"}) となっている状態での
// 計測値である。もし  $("#contents>li").css({display:"none"}) であれば
// 9から11 は全てゼロとなる。
 9: $("#contents>li:first").children().eq(0).width() //241
10: $("#contents>li:eq(1)").children().eq(0).width() //247
11: $("#contents>li:last").children().eq(0).width() //354

// インラインコンテンツそのものの測定その2
12: $("#contents>li:eq(1)").children().eq(0).css({display:"inline"}).width() //247
13: $("#contents>li:eq(3)").children().eq(0).css({display:"block"}).width() //604

// ol ブロック要素の高さ測定
14: $("#contents").css({display:"block"}).height() //54
15: $("#contents").css({display:"none"}).height() //54

// li ブロック要素の高さ測定
16: $("#contents>li:first").css({display:"block"}).height() //18
17: $("#contents>li:first").css({display:"none"}).height() //18
// li ブロック内のインラインコンテンツの高さ測定
18: $("#contents>li:first").children().eq(0).height() //18

以上から以下のように多くのことが分かります。

  1. 或るブロック要素の display を none として当該要素に対して jQuery(・・・).width メソッドを適用すると、その子孫要素である inline 要素の幅を計測することが出来る。子要素だけではなく孫要素であるインライン要素に対してもこの方法で計測できることに注目したい。

    このことは 8. や 11. との比較で分かるように、3. で得られた値が目次文字列の中の最大幅の文字列幅となっていることから結論づけられる。

    また、5.~8. の li ブロックの計測結果でも判然としている。

    但し、この方法は対象要素が表示されている場合にはそのままでは使えない。display:"none" と指定することによって要素を非表示にしてしまうからである。その場合には、インラインコンテンツそのものを計測する 9.~11. の方法を採用すればよい。

  2. 1. と 2. 及び 3. と 4. との比較でも分かるように、サイズ計測に visibility 属性は影響を与えない。よって5. 以降では visibility 属性は考慮外とした。
  3. 9.~11. ではインライン要素そのものを対象として計測したが、このように直接測ることも可能である。しかしこの方法は記述が長くなるので計測対象が表示されている場合以外には、決して推奨できないだろう。
  4. 12. では敢えて inline 属性を与えて取得してみたがそれは 10.の結果と変わらなかった。つまり 12. の方法は意味がない。
  5. 14. ~ 18. で分かるように高さの取得については、display 値が block であろうが、none であろうが結果が変わらない。しかも複数行の場合もチェックしてみたが、やはりインラインコンテンツ高さは同じ値となった。つまり、高さに関しては display 値に無関係にインライン高さサイズが取得できた。

それにしてもどうしてこのように動くのか?、それを解明する必要があります。

ルールを知っておかないと jQuery(・・・).width()/height() メソッドを自在に運用することは出来ないからです。

次のエントリイでこの「ルール」について触れてみたいと思います。

複数の 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にする場合の指定

複数エントリイにまたがるコンテンツの総合目次をスクリプトで挿入する

はじめに

複数のエントリイにまたがって或る長いコンテンツがあるとする。この場合、全体に共通する総合目次が存在すると閲覧しやすい。

そしてその総合目次は、どのコンテンツが今見ているエントリイに存在し、また別のエントリイにどんな項目が存在しているかを指し示すべきであり、別ページの項目にはリンクが貼られていることが望ましい。

以上の総合目次をスクリプトで挿入する方法を考えてみた。

この総合目次を実際に適用したエントリイは、上の関連エントリイリストで示した No.749~753 の 5 つだ。

それらのいずれかを開けば、実際にスクリプトによって挿入された総合目次をエントリイ最上部に見ることが出来る。

何故 Ajax を使わなかったか?

まず Ajax 通信を使おうと考えたが直ぐに止めた。答えは単純だ。使えないからである。

Fc2 ブログの場合、エントリイHTMLファイルが存在するサーバーと、アップロードしたファイルが存在するサーバーが異なっている。このため同一サーバー上のファイルにしか適用できない Ajax 通信はアップロードしたファイルを対象として使用できないのである。

もし、Ajax 通信が同じ Fc2 ドメイン内の異なるサーバー間でも使用できるならば、総合目次をアップロードしておいて、それを jQuery(要素).load(url) で当該要素内に読み込めば済む。しかし、駄目元で試したみたがやはり「期待通り」駄目だった。エントリイサーバーと異なるサーバーを対象として、エントリイ頁から Ajax 通信は出来なかった。

総合目次の HTML 文をスクリプト内に取込み、script をインクルードすれば良い

では、諦めて各エントリイに同一の総合目次 HTML 文を挿入するしかないのか?!─── Ajax 通信が不成功に終わった直後はそう考えた。

しかし、それでは余りに芸がない。何とかならないものかと思案していたら、ふと script タグによるインクルードならば、Ajax 通信と異なり、同一サーバー制限がないことを思い出した。

そうだ。HTML 文を Javascript 文で書いてしまって、全てをスクリプトで挿入すればよいのだ!

どっちみち HTML 文だけでは目的は達成できずJavascript コードを必要とするのだから、全てをスクリプト化してしまえば良いのだ!

こうして完成した総合目次は、エントリイ No.749~753 に掲載した。

▲ToTop

目次の HTML 文を取込みインクルード対象とした script について

極めて簡単なスクリプトであり、目次部分を変更すればいくらでも汎用的に使えるので、script を掲載し説明することとしたい。

■ Entry No.749~753 の総合目次の Javascript 文
// 目次を挿入する div 要素を取得し、ショートカットを作る。
// この要素もスクリプトで挿入しても良いが、可読性を高めるために敢えてエントリイ HTML 文
// 内に別途挿入することとした。なお、HTML文に挿入した div 要素はあらかじめ display:none
// としておく必要がある。
var $contents = $("div.posSizeContents");
// 個別エントリイ表示モードではない時には、必要なコメント文を挿入する。
if (/blog-entry.+html$/.exec(location.href)===null){
  $contents.html('<p class="ta_c">個別エントリイ表示モードの時のみ、ここに目次を表示します</p>').show();

} else { // 以降は個別エントリイモードの時にのみ適用されるブロック
// 変数 contents に総合目次のHTML文を登録する。
var contents ='<ol class="ml_0 pl_2 mt_0 mb_0">'+
'<li><a href="http://hkom.blog1.fc2.com/blog-entry-749.html" target="_blank">要素位置の測定と適正な配置──はじめに</a></li>'+
'<li><a href="http://hkom.blog1.fc2.com/blog-entry-750.html" target="_blank">コード解説(1) jQuery.offset</a></li>'+
'<li><a href="http://hkom.blog1.fc2.com/blog-entry-750.html" target="_blank">コード解説(1) jQuery().offset</a></li>'+
'<li><a href="http://hkom.blog1.fc2.com/blog-entry-751.html" target="_blank">コード解説(2) jQuery().position</a></li>'+
'<li><a href="http://hkom.blog1.fc2.com/blog-entry-751.html" target="_blank">コード解説(2) jQuery().offsetParent</a></li>'+
'<li><a href="http://hkom.blog1.fc2.com/blog-entry-752.html" target="_blank">コード解説(3) jQuery().scrollLeft</a></li>'+
'<li><a href="http://hkom.blog1.fc2.com/blog-entry-752.html" target="_blank">コード解説(3) jQuery().scrollTop</a></li>'+
'<li><a href="http://hkom.blog1.fc2.com/blog-entry-753.html" target="_blank">コード解説(4) jQuery().innerHeight</a></li>'+
'<li><a href="http://hkom.blog1.fc2.com/blog-entry-753.html" target="_blank">コード解説(4) jQuery().innerWidth</a></li>'+
'<li><a href="http://hkom.blog1.fc2.com/blog-entry-753.html" target="_blank">コード解説(4) jQuery().outerHeight</a></li>'+
'<li><a href="http://hkom.blog1.fc2.com/blog-entry-753.html" target="_blank">コード解説(4) jQuery().outerWidth</a></li>'+
'<li><a href="http://hkom.blog1.fc2.com/blog-entry-753.html" target="_blank">コード解説(4) jQuery().height</a></li>'+
'<li><a href="http://hkom.blog1.fc2.com/blog-entry-753.html" target="_blank">コード解説(4) jQuery().width</a></li></ol>';

// 正規表現作成
var  regExpr = /(http:.+entry-)([0-9]+)/,
// アドレス情報から今見ているエントリイの番号を取得
  thisEntryNo = Number(regExpr.exec(location.href)[2]);
// 用意した div 要素内に総合目次 HTML 文を挿入してから、a 要素を見つけ、
// 全ての a 要素を対象としてイテレート処理を施す。
$contents.html(contents).find("a").each(function(){
  // href属性値から エントリイ番号を取得して変数 No に代入する
  var No = Number(regExpr.exec($(this).attr("href"))[2]);
  // 取得した No が今見ているエントリイの番号と一致すれば、
  if (No===thisEntryNo){
    // 当該 a 要素の内容テキスト(つまりエントリイタイトル)を変数 txt に代入
    var txt = $(this).text();
    // a 要素の親要素に遡り、その中を空にしてから(つまり a 要素を削除してから)
    // 当該エントリイタイトル名称に「 ……このエントリイで解読します」を結合した
    // テキスト分を挿入する。
    $(this).parent().empty().html("<span class='pink'>"+txt + " ……このエントリイで解読します</span>");
  } else { // No !== thisEntryNo の時には
    // a 要素の後に「 ……別のエントリイ No."+ No +" で解読します」なるテキスト文を付加する。
    $(this).after("<span> ……別のエントリイ No."+ No +" で解読します</span>");
  }
}).end().show(); //総合目次を挿入した div 要素を非常時から表示に変更する
}

Ajax 通信に拘って総合目次を読み込む方法

上で示した方法よりも時間を要することになるが、Ajax 通信で同一のことを行う方法がないわけではない。

総合目次はエントリイタイトルとエントリイアドレスから構成されているのだから、これらの情報は目次を挿入したいエントリイと同一のサーバーにある。つまり、Ajax 通信の適用対象となる。

よって、総合目次のエントリイ情報を Ajax 通信で取得すればよいことになる。

実は、Ajax 通信によるエントリイ情報取得は、ナビゲータブロックで既に実施済みであり、このコードを一部手直しするだけで、ここで作成したエントリイNo. 749 ~ 753 の総合目次を作成することが可能ではある。

しかし、この方法の場合、エントリイタイトルをそのまま利用することになるので、目次項目を自由に加工した総合目次とするわけには行かない。

だから、ここでは敢えて HTML文を組み込んだ script をインクルードする道を選択したのである。

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

興味深い書籍『Javascript:The Good Parts』について

Amazon カスタマーレビューに綴られている通り、この書籍は決して入門者向けではないが、『JavaScript 第5版』のように分厚くはない。こちらが網羅的な教則本であるとすれば、『JavaScript:The Good Parts』はラジカルな内容があっさりと既述されている、随筆的なエッセンス本と言えるだろう。

内容がラジカルである上に、訳の性でもないだろうが、決して読みやすい書籍でもないが、それにも拘わらずこの書籍は読むに値する良書だ。

特に、"prototype 継承" に関する著者の主張は傾聴に値するし、「出来るだけ new 演算子の使用を避けてインスタンスを生成すべきである、との主張も興味深い。

エントリイRSS の xml ファイル取得コードを見直した

RSS xml ファイルのAjax通信に拠る取得方法を全面的に見直した

2 年程余り前に、始めて拙ブログの xml ファイルを Ajax 通信で取得してみた。最新エントリイの諸情報を取得するのだが、それ自体が目的ではなく飽くまでも Ajax 通信を試してみたかったのだ。

そして当時はそれなりの目的を果たせていたのだが、最新版の jquery.js(1.4.2)を使うようになって改めて 2 年前のコードを試した見たところ、当時のように動かない事が判明したため、改めてコーディングを修正する必要に迫られた。

そこで、久しく接していなかった jquery.js の Ajax 通信コードの適用を改めて学習する羽目となったのだった。

▲ToTop

その結果は前のエントリイにまとめた

修正は相当全面的となったが、その結果は以前のエントリイを改訂することによって、比較できるようにした。

冒頭に掲げた関連エントリイがそれだ。

 90%近いシェアを握っているインターネットエクスプローラの描画エンジンを利用したタブbrowser。沢山のタブbrowserがあるが、多機能、カスタマイズフリー、スクリプト利用等で一日の長がある。Gekkoエンジンへの対応も行われ、IEからの自立独立の方向に向かっている。2005年7月にはIE7が登場する見通しの中で、今後の発展が望まれる。

 多様なCSS作成支援機能を備えた、タグ入力式 HTML&CSS作成支援エディタ。スキンデザインもすっきりしている。テキストエディター上で作成するよりも確実で安全にタグ打ちが出来る。
文字コードを選べないのが欠点。

 StyleNote同様のタグ入力式 HTML&CSS 作成支援エディタ。長年使用してきたが現在StyleNoteに乗り換えつつある。

 クリップボード履歴情報を活用する為のソフト。画像まで履歴を取ってくれるのが嬉しい。このソフトを使わない日は絶対ない程に重宝し、愛用している。

 起動中のウィンドウの「コピーできない」説明文などの文字列を取得し、コピー可能な文字データにするツール。何かと便利。

 ストリーミングデータを保存することが出来るソフト。動画利用には不可欠なソフトだ。

 無料ながらレイヤー機能を有し、スクリプトによる拡張も可能な、sleipnir作者が提供している優れもの画像編集ソフト。

 画面キャプチャソフトと言えばこれに勝るものなし、ではないだろうか? 様々な取得方法を有しており、ブログ作成にもHomepage作成に不可欠だ。Jtrimと並んでWoodyBellsの作品。

 複数ファイルの同時編集は出来ないが、透過pngも作れる画像編集ソフト。
(以下当該サイトから抜粋)初心者にも簡単に操作が出来るフォトレタッチソフトです。多くの加工機能で画像に様々な効果を与えることができます。非常に軽快に動作するため、ストレスなく操作できます。

 Animation Gifファイルを作れる無料ソフト。

 キャプチャソフト。画面内にサイト全体が表示しきれない場合でも、これを使えば全体をキャプチャすることが出来る。

 画像処理。画像のフォーマット変換のみならず、色数やサイズ、圧縮率の変更まで一括処理できてしまう『BatchGOO!』は、大量の画像をまとめて処理したいときに大変便利なソフト。BMP, TIFF, JPEG, PCX, PNG の相互変換をはじめ、色数・サイズ・解像度の統一、JPEG圧縮率の調節など、ホームページ用の画像や携帯電話用の壁紙を揃えるのに抜群の相性を見せる。(Vectorの当該ソフト紹介頁より抜粋引用)

 名前から直ぐに想像が付くように画像のサイズを測るためのソフトだ。Homepage作成には欠かせない。2カラム、3カラムのレイアウトを行う場合に大変重宝する。

 ランチャーソフトは沢山あるが、中でもこれが一押しだ。2年以上使ってきたがその操作性には毎日満足している。これを使い始めてからデスクトップには一切のアイコンを表示することをやめてしまった。

 AdobeReader7によって、起動時間が長すぎるという長年のユーザーの不満はある程度解消した。そのためこの高速化ソフトは存在価値が低下してしまったかもしれない。AdobeReader6迄はこのソフトによる起動高速化で恩恵を受けてきた。

 IE専用が難点だが、様々なサイト内でIDやパスワードを入力するのに重宝するソフト。コンテキストメニューから簡単に起動できるのがGood! sleipnir等のIEの描画エンジンを利用しているブラウザでも使える。

 利用しているパソコンの諸元値を取得するには、このソフトがベストだ。インストール済みソフトの一覧が取得できるのも嬉しい。

 WMPは機能が豊富なだけ重い。RealPlayerも同様だ。そこでMedia Player Classicを使いたい。動作が軽快なだけではなく、対応しているファイル形式もすこぶる多く、これひとつで、wmvもrmも表示できてしまうのだから凄い! 数多あるMedia Playerの王様と言えるだろう。

 自宅でPCを起動しているときには必ず起動しているメディアプレーヤー。何かと過剰なWinampよりも、起動も速くスキンはシンプルだ。

 DivX, Xvid, Mov, Vob, Mpeg, Mpeg4, avi, wmv, dv, などの動画をDVD-Video形式に変換できるフリーソフト。クリックするとDVD関連ソフト紹介サイト=「DVDなToolたち」なるHomepageが開きます。

----------
201506021301
201211111111
201105010854
201012150013
201012082037
201012041751
201011250125
201011220020
201011150534
201011120654
201011112355
201009052249
201008300123
201008080834
201007272344
201007150038
201007130802
201007090028
201006050945
201005170106
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。