« Ruby on Rails を使ってひとりでサービスを作ってみよう | メイン | ローディングが素敵なウェブサイトを紹介します »

「Zooomr」の「写真の上に別の写真のレイヤーを配置する」部分をクラスライブラリ化してみましたはてなブックマークに追加 livedoorクリップに追加 Yahoo!ブックマークに追加 del.icio.usに追加 イザ!ブックマーク ニフティクリップに追加

こんにちは。
JavaScriptを担当している竹村です。

JavaScriptのライブラリは、進化が早いのでいろいろなライブラリを使ったサイトについて解説したり、サンプルを作ったりしながら、どのようにライブラリを利用しているのか参考にしてもらえればと思います。

今回のお題

このつくるぶに8/20に掲載された「特集 Flash|Ajax ベストサイトセレクション20」に私も参加させていただいているのですが、この対談ではプログラム的な話はあまりしていないためAjaxパートについて、JavaScriptの技術面のフォローを弊社のブログにて書いています。

つくるぶ ベストサイトセレクション20技術フォローまとめ -ARK-Web SandBox Wiki

ちょうど今朝の投稿で4回目になり、次回でラストの予定です。


さて、上記4回目の記事に書きました「お題7>写真/動画で魅せるサイト」の「Zooomr」の「写真の上に別の写真のレイヤーを配置する」という部分を簡単に自作してみました。

Crosslayer サンプル
http://staff.ark-web.jp/~takemura/public/js/crosslayer/

今回のつくるぶガイドブログでは、これをprototype.js 1.6.0 RC1を利用してクラス化をしてみようと思います。prototype.js 1.6.0 RC1は2007/10/16にリリースされた現在最新のリリース候補バージョンです。
※これで1.6.0についてFIXされたわけではないのでリリースにあたって仕様が変わる可能性があります。

まず、どういうライブラリを作る?

今回の説明用に、Crosslayerをライブラリ化したサンプル事例を作ってきました。

Crosslayerサンプル事例:
http://staff.ark-web.jp/~takemura/public/js/crosslayer/example_1.6.0rc1/
img02.jpg

アークウェブの最寄り駅の1つである『銀座一丁目』から『ビル前』までをアクセスマップのように、遷移していくものです。写真の中の四角い枠にマウスオーバーすると、次の写真がチラ見でき、クリックで先に進みます。

では、具体的にポイントとなるコードを見ていきます。

Crosslayerクラスライブラリを Prototype.js 1.6.0 RC1 で作ろう

構造について

まずは、レイヤー構造とどのレイヤーを何と呼ぶかを定義します。

img05.jpg

  • 最初に表示しておく画像が「1.背景画像レイヤー」
  • マウスオーバーしたら穴から背景画像がいじれる「2.穴レイヤー」
  • 穴レイヤーから表示される「3.(穴レイヤー内の)画像レイヤー」

仕組みとしては、「2.穴レイヤー」の下に「3.画像レイヤー」があり、穴から見えない箇所は非表示になっています。
その下に「1.背景画像レイヤー」がある形で、「3.画像レイヤー」が表示されるのは「2.穴レイヤー」にマウスオーバーしている時だけです。また、「3.画像レイヤー」をクリックしたら、次の画面に遷移します。

初期準備

まずは initialize 関数で最初の画像とメッセージを表示します。
prototype.jsを使わない場合、

window.body.onload = initialize;

とイベントハンドラを表記していましたが、prototype.jsのEvent.observeというイベント用のメソッドを下記のように利用できます。

document.observe("dom:loaded", initialize, false);

この"dom:loaded"の部分は、DOMツリーの完成を待たずにイベントハンドラを実行できる記述です。

inizialize関数の中で実行している makeBackImage関数は下記のようになっています。

function makeBackImage(oLayerInfo) {
$('back-image').setStyle("width:#{imageWidth}px; height:#{imageHeight}px;".interpolate(oLayerInfo));
$('back-image').innerHTML = '<img src="#{imagePath}" width="#{imageWidth}" height="#{imageHeight}" /><p>#{message}</p>'.interpolate(oLayerInfo);
}

oLayerInfoには下記で示すレイヤー情報の1件分が入っています。$('back-image')は「1.背景画像レイヤー」です。

prototype.jsの機能が2つ実装されています。1つは『エレメント名.setStyle("スタイル形式");』というsetStyleの拡張がなされており、CSSと同じように1行で指定できます。

もう1つはテンプレート機能。文字列中に #{置換文字} を入れておき、interpolateメソッドで指定した引数のハッシュキーに対応する値が置換されます。

この2つは便利なので以降でも頻繁に使うので覚えて置いてください。

データ構造

今回は、下記のようなJSON構造のデータを用意しました。

このようなデータはAPIから呼び出せるようにするとか色々とやりようがありますが、とりあえずJSONの扱いはまた後日としましょう。

レイヤー情報データで、aLayerInfoList というグローバル変数を定義したので、今回はこれを使います。形式は下記の通りです。

  • 「link」にクリックされたときのaタグのhref属性の中身を書きます
  • 「imagePath」は「3.画像レイヤー」に表示する画像パスです。
  • 「imageWidth, imageHeight」は「3.画像レイヤー」のサイズ
  • 「offsetX, offsetY」は「2.穴レイヤー」の表示位置
  • 「layerWidth, layerHeight」は「2.穴レイヤー」のサイズ
  • 「message」は画像の下に表示する文字列です
オブジェクト生成

initialize内のCrosslayerオブジェクトを生成する部分です。利用側は普通にインスタンスを生成する形でOKです。

oCrosslayer = new Crosslayer();

prototype.js 1.6.0 RC1を利用したクラスの定義の仕方は、下記の形式で実装します。

Crosslayer = Class.create({
initialize: function(){ /* コンストラクタ */},
myFunc: function() { /* 独自メソッド */},
lastFunc: function() {}
});

※lastFuncメソッドの } に ,(カンマ)が付いていないことだけ注意してください。
perl等の言語からJavaScriptコーディングをする際によく間違います。

クロスレイヤーの生成

次に、index.htmlのmakeLayer関数で「2.穴レイヤー」と「3.画像レイヤー」を生成します。

makeLayer: function(oBackImageElement, oLayerInfo) {
// offsetX, offsetYの変換
var oNewLayerInfo = this._convertOffsetPosition(oBackImageElement, oLayerInfo);

// メンバ変数へ保存
this._oLayerInfo = oNewLayerInfo; // レイヤー情報

// 穴レイヤーを生成する
this._generateHoleLayer(oBackImageElement);

// 穴レイヤーに画像レイヤーを埋め込む
this._generateInnerLayer();

// イベントハンドラを定義
Event.observe(this._oHoleLayerElement, "mouseover", this.startMousemove.bind(this), false);
Event.observe(this._oHoleLayerElement, "mouseout", this.endMousemove.bind(this), false);
},

offsetX, offsetYの変換については端折ります。
(レイヤー情報のoffsetX,offsetYについてページの左上を原点に変換しています)

穴レイヤーを生成する部分を説明します。

_generateHoleLayer: function(oBackImageElement) {
// 穴レイヤーの生成
var oHoleLayer = document.createElement('div');
oBackImageElement.appendChild(oHoleLayer);
Element.setStyle(oHoleLayer, "position:absolute; overflow:hidden; border:solid 1px #000000;");
Element.setStyle(oHoleLayer, "width:#{layerWidth}px; height:#{layerHeight}px;".interpolate(this._oLayerInfo));
Element.setStyle(oHoleLayer, "left:#{offsetX}px; top:#{offsetY}px;".interpolate(this._oLayerInfo));

// メンバ変数へ保存
this._oHoleLayerElement = oHoleLayer; // 穴レイヤー
},

穴レイヤーは、「1.背景画像レイヤー」の上に表示するので position:absolute を指定し、穴から溢れた部分は表示させたくないので overflow:hidden を指定しています。

また、穴の枠の表示はしておきたいので、border:solid 1px #000000 を指定しています。

後は、レイヤーサイズと位置の設定を例のinterpolateで文字列置換しています。

次に、穴レイヤーに画像レイヤーを埋め込む部分を説明します。

_generateInnerLayer: function() {
// 穴レイヤー内の画像レイヤーの生成
var oInnerLayerElement = document.createElement('div');
this._oHoleLayerElement.appendChild(oInnerLayerElement);
oInnerLayerElement.innerHTML = '<a href="#{link}"><img src="#{imagePath}" width="#{imageWidth}" height="#{imageHeight}" border="0" /></a></div>'.interpolate(this._oLayerInfo);
Element.setStyle(oInnerLayerElement, "position:absolute");
// oInnerLayerElement.setStyle("display:none"); // <- IEではdisplay:noneにするとmouseoverイベントが呼ばれない
this._setOpacty(oInnerLayerElement, 0);

// メンバ変数へ保存
this._oInnerLayerElement = oInnerLayerElement; // 穴レイヤー内の画像レイヤー
},

innerHTMLの中で、<a>タグと<img>タグを生成しています。

この画像も、穴レイヤーに対して相対位置でセットしています。これはマウスオーバーされた時に、この画像のleftとtopを移動させるためです。

= ちょっとTips =
1つコメントになってますが、display:none にすると、IEでは『マウスオーバーが非表示部分に対して取れなくなる』という問題があり、急きょ透過する方法に変えています。

この部分を display:none にしたサンプルがこちらにあります。

実行してお分かりのように、IEでは1pxの枠にしかマウスオーバーが反応しないので、勢い良くマウスを穴レイヤーの中に持って行くと、マウスオーバーのイベントが実行されません。

イベントハンドラの設定と解除

上記の「クロスレイヤーの生成」のコードに、イベントハンドラのセットについて書いてありました。
内容はマウスオーバーとマウスアウトの設定です。

↓こちらにもう一度書きます。

Event.observe(this._oHoleLayerElement, "mouseover", this.startMousemove.bind(this), false);
Event.observe(this._oHoleLayerElement, "mouseout", this.endMousemove.bind(this), false);

また、マウスオーバー中にマウスが移動したのを検知するハンドラは、
startMousemoveメソッドに↓このように書いてあります。

startMousemove: function() {
// Element.show(this._oInnerLayerElement); // <- IEではdisplay:noneにするとmouseoverイベントが呼ばれない
this._setOpacty(this._oInnerLayerElement, 1);
Event.observe(this._oHoleLayerElement, "mousemove", this.mouseMove.bind(this), false);
},

this._oHoleLayerElementは「2.穴レイヤー」のことです。そのレイヤーに対してイベントを割り当てています。

bind(this) は、イベントハンドラ側のthisの指定をCrosslayerオブジェクトに指定するためにつけています。

マウスが移動したときのイベントハンドラである mouseMoveメソッドは、現在のマウス座標からレイヤー内の画像のどの部分を表示するかを計算して「3.画像レイヤー」のleftとtopの位置を再指定するだけなので、コードは割愛します。

おわりに

Zooomrの仕様とはアチコチ違いますが、壮大な仕様も少しずつ分解して実装していけば、今回のようなサンプルは1日で作れます。あとは根気で作っていく力とアイデア次第で面白い実装やライブラリ作成ができるようになると思いますよ。

さて、今回は 1回目なのでかなり気合入った長文になりましたm(_ _)mスミマセン。
次回はもう少し抑え目になると思います^^;