今回は、画像のロールオーバーを、今まで説明してきたように、クラスを使いながら作ってみます。リンクにマウスオーバー(及びフォーカス)されると、中にある画像のsrcの "_normal." を、 "_over." に変更してセットし、マウスアウト(及びブラー)されると、これと逆のことをします。
<ul>
<li><a class="rollover" href="http://diablo.com"><img src="diablo_normal.gif" alt="Diablo" /></a></li>
<li><a class="rollover" href="http://hoge.com"><img src="hoge_normal.gif" alt="HOGE" /></a></li>
<li><a class="rollover" href="http://zudolab.net"><img src="zudolab_normal.gif" alt="zudolab" /></a></li>
</ul>
/**
* 接尾語
*/
var suffix = {
normal: "_normal.",
over: "_over."
};
/**
* ロールオーバーマネージャー
*/
var RolloverManager = function(){
this.pool = []; // アイテム保存用
};
RolloverManager.prototype = {
register: function(container){
var set = new RolloverItem(container); // RolloverItemを作る
this.pool.push(set); // poolに保存
}
};
/**
* ロールオーバーアイテム
*/
var RolloverItem = function(container){
this.container = container; // コンテナをメンバ変数として保存
this.img = this.container.find("img"); // 画像を探して保存
this.setEvents(); // イベントをセット
this.preload();
};
RolloverItem.prototype = {
setEvents: function(){
var self = this;
// mouseover, focus時にchgToOverを呼ぶ
this.container.bind("mouseover focus", function(){
self.chgToOver();
// mosueout, blur時にchgToNormalを呼ぶ
}).bind("mouseout blur", function(){
self.chgToNormal();
});
},
chgToOver: function(){
// _normal. を _over. に置換してセット
var src = this.img.attr("src");
if(src.indexOf(suffix.normal)==-1) return;
src = src.replace(suffix.normal, suffix.over);
this.img.attr("src",src);
},
chgToNormal: function(){
// _over. を _normal. に置換してセット
var src = this.img.attr("src");
if(src.indexOf(suffix.over)==-1) return;
src = src.replace(suffix.over, suffix.normal);
this.img.attr("src",src);
},
preload: function(){
// _normal. を _over. に置換してimg要素を作る
// 実際にこれはドキュメント上に配置されないが、画像はキャッシュされる
var src = this.img.attr("src");
if(src.indexOf(suffix.normal)==-1) return;
var img = new Image();
img.src = src.replace(suffix.normal, suffix.over);
}
};
/**
* セットアップ
*/
$(function(){
var manager = new RolloverManager();
// a要素をラップしたjQueryオブジェクトを1個ずつmanagerにregister
$("a.rollover").each(function(){
manager.register($(this));
});
});
今回のソースでは、a要素それぞれにつき、RolloverItemのインスタンスを作成します(今までのまんじゅうにあたる)。今回は、前回のように、poolに保存しておく必要はありませんが、ひとまずソースコードを見ていきます。
まず、今回は、マウスオーバー、アウトで、"_normal."、"_over." を切り替えるので、これが変更されたりしても面倒の無い様、変数として宣言しておきます。
/**
* 接尾語
*/
var suffix = {
normal: "_normal.",
over: "_over."
};
変数がごちゃごちゃあると気持ちが悪いので、連想配列にしておきます。これで、suffix.normal、suffix.overで、文字列をもってこれます。
前回のまんじゅうマネージャーと同様、ロールオーバーマネージャーを作成します。
/**
* ロールオーバーマネージャー
*/
var RolloverManager = function(){
this.pool = []; // アイテム保存用
};
RolloverManager.prototype = {
register: function(container){
var set = new RolloverItem(container); // RolloverItemを作る
this.pool.push(set); // poolに保存
}
};
ここは前回とほぼ同様ですね。containerとして、jQueryオブジェクトを渡します。スクリプトの最後のところで、
/**
* セットアップ
*/
$(function(){
var manager = new RolloverManager();
// a要素をラップしたjQueryオブジェクトを1個ずつmanagerにregister
$("a.rollover").each(function(){
manager.register($(this));
});
});
と、classにrolloverが指定されているa要素それぞれにつき、jQueryオブジェクト化し、ロールオーバーマネージャーに渡しています。
ロールオーバーマネージャー内で作られているのが、このロールオーバーアイテムのインスタンスです。
/**
* ロールオーバーアイテム
*/
var RolloverItem = function(container){
this.container = container; // コンテナをメンバ変数として保存
this.img = this.container.find("img"); // 画像を探して保存
this.setEvents(); // イベントをセット
this.preload();
};
コンテナ(a要素)、その中の画像を保存し、setEvents(イベントをセットするメソッド)、preload(画像のプリロードを行うメソッド)を読んでいます。
コンテナ(a要素)に、イベントをセットします。
setEvents: function(){
var self = this;
// mouseover, focus時にchgToOverを呼ぶ
this.container.bind("mouseover focus", function(){
self.chgToOver();
// mosueout, blur時にchgToNormalを呼ぶ
}).bind("mouseout blur", function(){
self.chgToNormal();
});
},
jQueryの便利機能、bindを使うと、複数のイベントを同時にセットできます。ここでは、マウスオーバー、フォーカス時に、画像をオーバー状態に、マウスアウト、ブラー時に、画像を通常の状態に変更させるメソッドを呼ぶようにしています。
イベントが発生したときに呼ばれるのが、この chgToOver、chgToNormal メソッドです。ここでは、imgのsrcを変更する処理をしています。
chgToOver: function(){
// _normal. を _over. に置換してセット
var src = this.img.attr("src");
if(src.indexOf(suffix.normal)==-1) return;
src = src.replace(suffix.normal, suffix.over);
this.img.attr("src",src);
},
chgToNormal: function(){
// _over. を _normal. に置換してセット
var src = this.img.attr("src");
if(src.indexOf(suffix.over)==-1) return;
src = src.replace(suffix.over, suffix.normal);
this.img.attr("src",src);
},
もし、オーバー状態にするときに_normal.が無かったり、通常状態に戻すときに_over.が無かったりした場合、処理をすること自体に意味が無いので、これらの文字列がsrcに含まれていない場合は、returnで処理を終わらせています。
イベントが発生したときに、画像のsrcを変えますが、このとき、画像があらかじめ読み込まれていないと、マウスオーバーしたときに初めてオーバー状態の画像を読み込むことになってしまい、一瞬で画像が切り替わりません。あらかじめ、画像の要素を作り、オーバー状態のsrcをセットすることで、ブラウザのキャッシュに、オーバー状態の画像を残します。
preload: function(){
// _normal. を _over. に置換してimg要素を作る
// 実際にこれはドキュメント上に配置されないが、画像はキャッシュされる
var src = this.img.attr("src");
if(src.indexOf(suffix.normal)==-1) return;
var img = new Image();
img.src = src.replace(suffix.normal, suffix.over);
}
これは、RolloverItemのコンストラクタ内で呼ばれるため、ロールオーバーをセットしたときに、このプリロードも行われる仕組みになっています。
・・・といった具合で、ロールオーバーマネージャーを介し、ロールオーバーアイテムインスタンスを作っていくという風に書くことができます。まぁしかし、この方法で書いても、ロールオーバーだとありがたみがあんまりないですね~。そんなに大層でない機能であれば、イベントをセットしてしまえばそれで終わりなことが多いですからね。 (この場合だと、初めにaとimgを取得しているので、イベントが発生するたびにDOMから何度もimgを探してこなくて良い&画像変更のメソッドはprototypeでつけられているので、無駄が無いみたいなこまい話ぐらい?)
この場合、このように書いておくと便利なのは、たとえば、全てのロールオーバーを止めたいということが発生した場合(ロールオーバーアイテムクラスに、ロールオーバーを行うか否かを決定する変数を持たせ、イベント発生時にそれを見てロールオーバーする/しないという判別を行う)、
ダイナミックにページの内容が変化した後にもロールオーバーを設定したい場合(AjaxでHTML等をもってきて、ページに足された後に、それにイベントを設定する)、
システムか何かの都合で、"_normal", "_over" 以外のパターンでもロールオーバーを行わせたい場合(suffixの情報をRolloverManagerや、RolloverItemに持たせておき、複数のロールオーバーのパターンに対応させる)、
他の機能と連携した場合(ロールオーバーではなかなかないですねこれは。たとえば、チェックボックスに全てチェックを入れるという機能を作ったとしたら、コンテナを開いたらその機能を呼ぶといったような場合があります)
のように、単純なロールオーバーに、さらにもうちょっと何か機能が必要とされたときに、拡張しやすいかもしれません。イベントセットしておわり~で書いてあると、こういった場合に苦しむかもしれませんねー。うーん微妙な例だったか・・・
jQueryっぽく書くのであればこんな感じになるでしょうか?
$(function(){
$.fn.rollover = function(){
var suffix = {
normal: "_normal.",
over: "_over."
};
this.bind("mouseover focus", function(){
$(this).find("img").each(function(){
var img = $(this);
var src = img.attr("src");
if(src.indexOf(suffix.normal)==-1) return;
img.attr("src", src.replace(suffix.normal, suffix.over));
});
}).bind("mouseout blur", function(){
$(this).find("img").each(function(){
var img = $(this);
var src = img.attr("src");
if(src.indexOf(suffix.over)==-1) return;
img.attr("src", src.replace(suffix.over, suffix.normal));
});
}).find("img").each(function(){
var img = $(this);
var src = img.attr("src");
if(src.indexOf(suffix.normal)==-1) return;
(new Image).src = src.replace(suffix.normal, suffix.over);
});
};
$("a.rollover").rollover();
});
機能はほぼ同じですが、こちらは関数の入れ子入れ子で作っています(クロージャって言う)。うーん普通につかうのにはこっちの方が簡単で便利なんじゃなかろうかw。ただこの場合だと、それぞれの機能を個別に使うことはできませんね。最初のサンプルで用意されている、chgToOver、chgToNormal、preloadのように、1つの機能をメソッドを呼んで実行ということはできないです。
1個目のサンプルを作っていて、a要素にはイベントをセットしてるだけで、具体的な画像切り替えは、その中の画像で行ってる。っていうことは、画像には画像用のクラスを作ってやった方がすっきりするかなと思って作ったのがこのサンプルです。
var suffix = {
normal: "_normal.",
over: "_over."
};
var RolloverManager = function(){
this.pool = [];
};
RolloverManager.prototype = {
register: function(container){
var set = new RolloverContainer(container);
this.pool.push(set);
}
};
var RolloverContainer = function(container){
this.container = container;
var img = this.container.find("img").eq(0);
if(!img.size()) return;
this.img = new RolloverImg(img);
this.setEvents();
};
RolloverContainer.prototype = {
setEvents: function(){
var self = this;
this.container.bind("mouseover focus", function(){
self.img.chgToOver();
}).bind("mouseout blur", function(){
self.img.chgToNormal();
});
}
};
var RolloverImg = function(img){
this.img = img;
this.preload();
};
RolloverImg.prototype = {
hasNormalSuffix: function(){
return this.img.attr("src").indexOf(suffix.normal)!=-1 ? true : false;
},
hasOverSuffix: function(){
return this.img.attr("src").indexOf(suffix.over)!=-1 ? true : false;
},
chgToOver: function(){
if(!this.hasNormalSuffix()) return;
this.img.attr("src", this.img.attr("src").replace(suffix.normal, suffix.over));
},
chgToNormal: function(){
if(!this.hasOverSuffix()) return;
this.img.attr("src", this.img.attr("src").replace(suffix.over, suffix.normal));
},
preload: function(){
if(!this.hasNormalSuffix()) return;
(new Image).src = this.img.attr("src").replace(suffix.normal, suffix.over);
}
};
$(function(){
manager = new RolloverManager();
$("a.rollover").each(function(){
manager.register($(this));
});
});
このサンプルでは、サンプルその1でRolloverItemクラスになっていた部分から、画像用クラスを切り離し、RolloverContainer、RolloverImgという2つのクラスに分けています。画像に関する操作は全てRolloverImgクラスで行い、RolloverContainerではイベントのみセットするという形です。その1と比べると、こっちの方がすっきりしているかも?
そんな感じで、ロールオーバーでも色々な書き方ができます。ここで知っておいてほしいのは、ひとまず、こういう書き方があるということです。こー言う書き方があるっていうのを知らないと、なんのこっちゃさっぱりという状況に出くわしたとき、どうにもできませんし、配布されているライブラリも理解することができません。
また、複雑な機能を実装することになった時は、今回のサンプル1→サンプル3のように、機能を切りだして細かくしていかないと、巨大で、後から触りたくないスクリプトが出来上がってしまいます。
なんかまんじゅうが出てこないとまじめですねこれ・・・
次回はたぶん名前空間とか説明すると思います。
便利な点はちょっと先で~
続く・・・(たぶん)
---
【参考】
This article is about... JavaScript
metroider 2009/9/17 (12:38)
全部読ませていただきました!
今までオブジェクトっぽいJavaScritpを実際に作成したことがなかったのですが、おかげさまでやっと作り方を理解することができました(…多分)!
続きも是非期待しております。
Takazudo 2009/9/20 (04:05)
ありがとございます!
ちと最近超忙しく、なかなか書けていなくて放置状態になっているのですが、j管を見てまた書かせていただきますのでよろしくですー