[MooTools Tutorial] MooTools用プラグインの作り方

このエントリーはMooToolsチュートリアル特別編で、MooTools用プラグインの作成手順をステップバイステップで晒しています。
解説ソースの元にしているのはmooContreGalleryですが、
チュートリアルに使っているソースは実際配布しているものと仕様が異なります。

初歩的な説明はかっ飛ばしているので、詳しい解説については公式のドキュメントとか高橋文樹さんの日本語訳ドキュメントなどを参考にどうぞ。

プラグインを作るにあたっての前提と必要なファイルの用意

mooContreGalleryはhttp://www.contreforme.ch/のProjectsページで使われている
画像拡大、スクロール、インフォメーション表示など一連のエフェクトを実装するプラグインです。
元々「これどうやって作るの?」という質問に答えるために作ったものですが、結構いい感じに出来たので許可を頂いた上で公開するに至りました。
そういうわけなのでこのチュートリアルは質問に対する回答でもあります。

HTMLソースは本家とだいたい同じという前提ですが、すべてをスクリプト側で補うようには作っていないので
利用にあたってはCSSやソースにいくつか決まりごとがあります。

  • リスト要素(LI)の中に画像や説明が入っているとする。
  • リスト要素全体にクリックイベントを付与するので、cursor:pointerなどでクリック出来る事を示しておく。
  • サムネイルを入れている要素の背景にローディング画像を上下中央に設定しておく。
  • Fx.Scrollでスクロールさせる場合は下方向に余裕がないと途中でつっかえてしまうので、
    スタイルシートでpadding-bottom:600pxなどしておく。

MooToolsでは配布しやすくするためにClassを使って作るのが一般的。
プラグイン化するにあたって考えた仕様は次の通りです。

  • ギャラリーの主要な要素を別のタグに変更できるようにする
  • 要素を得る時に使うIDやクラス名は変更できるようにする
  • 画像ファイル名に含まれた任意の文字列でサムネイル(_s)と拡大画像(_b)を入れ替える

プラグインは任意で設定変更出来なきゃ意味がない。ってことで、これらはオプションで実現させました。

XHTMLファイルと画像、MooTools Core、Moreを用意。
Moreでは、画像読み込みをするのでAssets、開いたときにスクロールさせるのでScroll、説明をスライドさせるのでSlideが必要です。
(…と結論を書いてるけどMoreは作りながらリストアップしていく事が多い)

XHTMLソースの例:

<ul id="gallery"><!--ID指定するギャラリー-->
	<li><!--ギャラリーの子要素(自動取得)-->
		<a class="thumb" href="images/cat1.jpg"><img title="cat1" src="images/cat1_s.jpg" alt="cat1" width="100" height="68" />
		<!--サムネイル画像とそれを入れる要素。クラス名必須-->
		</a>
		<div class="info"><!--スライドされる説明要素。クラス名必須--></div>
	</li>
</ul>

noscript対策で拡大画像にリンクする場合は上記ソースのようにサムネイルを入れる要素がaタグになります。
サンプルでは上2つだけこのソースです。

続きから口調が投げやりなふいんきになります。

Step0: The base Class of the MooTools framework

MooToolsのクラスは、書き方が決まっているのでそれさえ覚えてしまえば作るのは簡単。
Coreで提供されるnewで始まる機能は全部クラスなので、圧縮されていないソースを直接見ると作る時の参考になる。

クラスの基本的な構文はドキュメントを参照して頂くとして、
新たに作るプラグインについて

  • 名前は? → mooContreGallery
  • オプションを使う? → YES → Implements: [Options]
  • 呼び出しどうする? → new mooContreGallery(“gallery”)にしたい → initializeにID入れる引数が必要

以上のように決めてクラスのベースを書くと次のようになる。

var mooContreGallery = new Class({

	//Optionsクラスを継承して実装
	Implements: [Options],

	//このクラスのオプション
	options: {
	},

	//初期化メソッド
	initialize: function(element, options) {
		//OptionsクラスのsetOptionsメソッドでクラスのオプションに渡されたユーザ定義オプションを追加
		this.setOptions(options);
	}
});

インスタンス作成時に new mooContreGallery("gallery"); として、UL要素につけたIDを必須にするため、
initializeの引数に、IDを格納するelementと、クラスオプションを格納するoptionsを指定する。

これがテンプレートみたいなものなので、スニペットとかに保存しとけばコピペでプラグインが作れるようになります。

【補足】initializeプロパティについて

PHPで言うところの__constructorです。

下記のようにdomreadyイベントなどでクラスインスタンスを作成すると、自動的にinitializeが1度だけ実行される。

window.addEvent("domready",function(){
	new mooContreGallery("gallery");
});

変数への代入や要素の取得などの初期処理を記述すれば呼び出されるたび初期値に戻る。
簡単な機能を提供するクラスであれば、initializeにすべて記述しても良い。

【補足】Implementsプロパティについて

Implementsをすると他のクラスの機能がコピーされる。
Implements: [Options] とした場合は、現在製作中のmooContreGalleryクラスに
Optionというクラスの機能を複製するので、クラス内には無いthis.setOptionsメソッドが使えるようになる。

Step1: ギャラリー要素の取得とエフェクトの決定

インスタンス作成時に指定されたIDを元に要素を取得する。

MooToolsで任意の要素を取得するには、ドル関数を使う方法と、
目印となる要素を1つ決めてMooToolsのElementメソッドで取得する方法がある。
後者はgetChildrenやgetFirstなどを使うため、IDやクラスを指定しなくてもいい反面HTMLソースの構造が変わると使い物にならないというデメリットがある。

「要素を得る時に使うIDやクラス名は変更できるようにする」と決めたので、オプションに変更可能な設定を入れておく。

options: {
	infoClass:".info", //スライドさせる要素のクラス
	thumbClass:".thumb" //サムネイル画像の親クラス
},

最後にカンマを付けるとIEでエラーが出るので注意。

とりあえず直接の子であるLI要素はgetChildren()で取得するが、その前に
インスタンス作成時に渡されたIDを持つ要素が無かったら実行しないようにreturnを置いておく。

var mooContreGallery = new Class({
	Implements: [Options],
	options: {
		infoClass:".info", //スライドさせる要素のクラス
		thumbClass:".thumb" //サムネイル画像の親クラス
	},

	initialize: function(element, options) {
		this.setOptions(options);
		if(!$(element)) return;//実行キャンセル
		this.lists = $(element).getChildren();//リスト要素取得
		//console.log(this.lists);
	}
});

これを実行してthis.listsに各リスト要素が格納されていればOK。
ここまでのサンプル

Step2: eachで配列を処理

ここからは編集中のソースだけ表示する。全文はサンプルを見てください。

リスト要素が変数に格納されたら、その子要素であるサムネイル画像と説明要素を処理する。
リスト要素は複数あり、その複数ある要素を1つずつ処理しなければならない。つまり反復処理だ。

反復処理でお馴染みはfor文だが、ここでは便利なeachを使う。
Arrayメソッドのeachは配列のみ、$eachはオブジェクトなどにも使用出来るという地味な違いがある。
eachを使うと、繰り返し実行される関数の引数に配列の要素やキー番号が格納されるので、関数内でそれらを使用することが出来る。

initialize: function(element, options) {
	this.setOptions(options);
	if(!$(element)) return;//実行キャンセル
	this.lists = $(element).getChildren();//リスト要素取得

	//elはリスト要素、indexはキー番号
	$each(this.lists,function(el,index){
	});
}

eachでLI要素を1つずつ処理して行うのはエフェクトを掛ける要素の取得と、インスタンスの作成。
このクラスで使う主なエフェクトと要素は次の3つ。

  • 説明が入っている要素(.info)→ 垂直方向スライド → Fx.Slide(More)
  • サムネイル画像(IMG) → 透明度変更と拡大縮小モーフィング → Fx.Morph
  • 上2つの親要素(LI)→ クリックされた時の自動スクロール → Fx.Scroll(More)

サムネイルの拡大縮小にはFx.Morphを使う。
何でMorphなのかというと、幾つかの効果を同時に与えなくてはならない為。1つだけならTweenで良い。
this.listsの下にFx.Morphオブジェクトを格納するメンバ変数morphを作成。空配列にしておく。

initialize: function(element, options) {
	this.setOptions(options);
	if(!$(element)) return;//実行キャンセル
	this.lists = $(element).getChildren();//リスト要素取得
	this.morph = [];//サムネイル画像のFx.Morphオブジェクトを格納する入れ物(配列)

	//elはリスト要素、indexはキー番号
	$each(this.lists,function(el,index){
});
}

Step2.2 サムネイル画像の取得

サムネイル画像をgetElementメソッドで取得して変数に一時保存する。
getChildrenやgetElementの引数にクラスやID、タグ名を指定すればその指定した要素だけ得る事が出来る。
サムネイル画像はリスト要素の子かつサムネイル要素の中にあるものでなければならないので、
まずel.getElement()でサムネイル要素を取得してから、getElementの引数にタグ名を指定して得る。
クラス名はあとで変更が出来るようにオプションにする。
設定したオプションはthis.options.名前とすれば値を得る事が出来る。
名前が長くなる時はメンバ変数に入れなおしても良い。

$each(this.lists,function(el,index){
var image = el.getElement(self.options.thumbClass).getElement("img");
});

Step2.3 配列のキー番号を保存

store()は任意のアイテムを保存出来る便利なElementメソッド。
el.index = index; と同等なので単純な値を保存するだけならstore()使わなくてもいいんだけど、
こういうのもあるよってことであえて使ってみる。

$each(this.lists,function(el,index){
var image = el.getElement(self.options.thumbClass).getElement("img");
el.store("index",index);
});

Step2.4 サムネイルのFx.Morphインスタンス作成

後で呼び出して実行するため、予め作っておいた配列にインスタンスを作っては入れ作っては入れしていく。
オプションにトランジションや遅延を新しく追加する。

options: {
	infoClass:".info",//スライドさせる要素のクラス
	thumbClass:".thumb",//サムネイル画像の親クラス
	thumbDration:700,//サムネイルの遅延
	tumbTransition:Fx.Transitions.Back.easeOut,//サムネイルのトランジション
},

配列に格納せずにクリックする都度インスタンスを作成するソースだと、オブジェクトが作られすぎて重くなってしまう。

$each(this.lists,function(el,index){
	var image = el.getElement(self.options.thumbClass).getElement("img");
	el.store("index",index);
	//サムネイルのFx.Morph
	self.morph[index]= new Fx.Morph(image,{duration: self.options.thumbDration, transition: self.options.tumbTransition});
});

Step2.5 クリックイベント

リスト要素にクリックイベントを追加する。

引数elにはリスト要素が格納されているので、addEventでクリックイベントを追加し
リスト要素のインデックス番号を表示させてみよう。
store()で保存した値はretrieve()で得る事が出来る。

$each(this.lists,function(el,index){
	var image = el.getElement(self.options.thumbClass).getElement("img");
	el.store("index",index);
	//サムネイルのFx.Morph
	self.morph[index]= new Fx.Morph(image,{duration: self.options.thumbDration, transition: 	self.options.tumbTransition});
	el.addEvent("click",function(e){
		e.stop();
		alert("open "+el.retrieve("index"));
	});
});

【補足】stop()について

addEventで実行される関数には引数でイベントオブジェクトが渡される。
冒頭でstop()しておくと、例えばリンクをクリックした時の移動などを行わないようにすることが出来る。

【補足】var self = this; について

グローバルな状態のthisはクラスオブジェクトそのものを指すが、
関数の中にあるthisは、その関数そのものだったり、要素だったり、windowだったりと、
クラスそのものを示すthisとは意味が違ってしまう。
bind()を使えばthisが示すオブジェクトは変更出来るものの、可読性が下がってややこしいので
ローカル変数に格納する方法を取る。

ここまでのサンプル

Step3: クリックで実行されるメソッドの作成

クリックイベントのalert()をクラスメソッドに変更する。名前は適当にopenとした。
$eachでサムネイル画像を取得しているので、引数でリスト要素(el)とサムネイル画像(image)を渡す。

//クリックイベント登録
el.addEvent("click",function(e){
	e.stop();
	self.open(el,image);
});

呼び出されるクラスメソッドopenの中であらかじめ作っておいたmorphをstartさせて、
透明度を変えるとローディングしてるっぽい雰囲気になる。
キー番号はretrieveメソッドでstoreした値を読み込む。

/**
* クリックで呼ばれる関数
* @param {HTML Object} el リスト要素
* @param {HTML Object} image サムネイル画像
*/
open:function(el,image){
	//console.log(image);

	var num = el.retrieve("index");
	this.morph[num].start({'opacity':0.3});
}

ここまでのサンプル

Leave a Comment.