2011 11 02

スパイダーを作るの2

好きな人には物凄く今更な話だろうが、攻殻機動隊。 草薙素子の巨乳と、その巨乳が作り出す谷間を強調する衣装は、視線誘導を目的としたものなんだそうだ。 油断を誘うとか何とか。

巨乳に改造して谷間を強調して視線誘導なんて、胸にかなりのコンプレックスがないと涌いてこない発想だよな。 察するに、元の彼女は貧乳だったんだろう。 彼が、デートのときはいつもすれ違う巨乳に目を奪われてたとか、隠してたエロ動画が巨乳ものばかりだったとか、きっと哀しい過去があるのだ。

しかし、改造して視線誘導に成功しても、いい気持ちになれたのは最初だけ。 男はどんどん馬鹿に見えてくるし、 「自分の魅力は作り物の巨乳だけ?」 なんて思いにはまり込む。 こうなるともう巨乳じゃなくて虚乳。 夢の抜け殻。 それでも、今更元には戻せない。

そんな妄想の糸を展開しながら、蜘蛛の続き。

カードゲームなんだから、まずはカードから。

ということでカードの実装方法だが、今だと選択肢は大きく img か canvas か css で頑張るかの3つになるのだな。 それぞれを評価するに、準備が楽なのは css, img, canvas の順。 イベント周りの処理が楽なのは、たぶん img, css, canvas の順。 拡大縮小まで考えると、css, canvas, img の順となるだろうか。 色々考えた結果、最初の調整が楽そうだってことで css でやることにした。

カード1枚の構成はこんな感じ。

<div class="cardOuter"> <div class="cardInner"> <div class="cardMark">♥</div> <div class="cardNumber">A</div> </div> <div class="cardMask" /> </div>

各要素の役割はこんな感じ。

cardOuter
カードの外枠。 カードの移動は、この外枠の移動として実装する。
cardInner
マークと数字のコンテナ。 表向きか裏向きかは、このコンテナを表示するかしないかの切り替えとして実装する。
cardMark
カードのマーク(♥ ♠ ♦ ♣)を表示する。
cardNumber
カードの数字(A, 2, …, 10, J, Q, K)を表示する。
cardMask
外枠に被せて最前面に配置し、マウスイベントを受け付ける。 マークや数字のそれぞれから上がってくるイベントを処理するよりも、専用のレイヤーを作った方が、管理もマルチブラウザ対応も楽そうだから。

この方針で実装したコードがこれ。 まずは css

.cardOuter { position : absolute; left : 0; top : 0; margin : 0; width : 60px; height : 80px; background : #e0e0e0; border : 1px solid #000000; overflow : hidden; } .cardOuter > div { position : absolute; left : 0; top : 0; margin : 0; width : 100%; height : 100%; } .cardMask { background : transparent; } .cardInner { background : #ffffff; font : normal normal normal 26px serif; } .cardInner > div { position : absolute; top : 0.1em; } .cardMark { left : 0.25em; text-align : left; } .cardNumber { right : 0.25em; text-align : right; letter-spacing : -0.1em; }

そして JavaScript

var CARD = { MARKS : { HEART : { mark : '&hearts;', color : '#ff0000' }, SPADE : { mark : '&spades;', color : '#000000' }, DIAM : { mark : '&diams;', color : '#ff0000' }, CLUB : { mark : '&clubs;', color : '#000000' } }, NUMBERS : [ 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K' ] }; function Card( mark, number ) { var self = this; function _createDiv( className, innerHTML ) { var div = document.createElement( 'div' ); div.className = className; if ( innerHTML ) { div.innerHTML = innerHTML; } return div; } var inner = _createDiv( 'cardInner' ); if ( CARD.MARKS[ mark ] && CARD.NUMBERS[ number ] ) { inner.style.color = CARD.MARKS[ mark ].color; inner.appendChild( _createDiv( 'cardMark', CARD.MARKS[ mark ].mark ) ); inner.appendChild( _createDiv( 'cardNumber', CARD.NUMBERS[ number ] ) ); } var mask = _createDiv( 'cardMask' ); mask.Card = this; var outer = _createDiv( 'cardOuter' ); outer.appendChild( inner ); outer.appendChild( mask ); this.getMark = function () { return mark; }; this.getNumber = function () { return number; }; this.setParent = function ( parentNode ) { parentNode.appendChild( outer ); return self; }; this.setZIndex = function ( zIndex ) { outer.style.zIndex = zIndex; return self; }; this.getZIndex = function () { return outer.style.zIndex; }; this.isOpen = function () { return inner.style.visibility === 'visible'; } this.open = function () { inner.style.visibility = 'visible'; return self; }; this.close = function () { inner.style.visibility = 'hidden'; return self; }; this.setPosition = function ( left, top ) { outer.style.left = left + 'px'; outer.style.top = top + 'px'; return self; }; this.getPosition = function () { return { left : parseInt( outer.style.left, 10 ), top : parseInt( outer.style.top, 10 ) }; }; this.focus = function ( focused ) { inner.style.backgroundColor = focused ? '#ffe0f0' : '#ffffff'; return self; }; var listeners = {}; this.addEventListener = function ( type, listener, capture ) { listeners[ listener ] = function ( e ) { listener.call( self, e ); }; mask.addEventListener( type, listeners[ listener ], capture ); return self; }; this.removeEventListener = function ( type, listener, capture ) { mask.removeEventListener( type, listeners[ listener ], capture ); return self; }; var info = {}; this.set = function ( key, value ) { info[ key ] = value; return self; }; this.get = function ( key ) { return info[ key ]; } }

使うのが spider なので、カードを列に重ねて置いたときにも下のカードが何だか判るよう、文字とマークを上の方にくるように css を定義した。 PCでの視認性は悪くない。 iPhoneだと、拡大しないと何だか判らないな。 まあ、拡大すればいいか。

試行錯誤しているうちに、JavaScript としては何だか微妙な感じになってしまったな。 以下、自分の中でモヤモヤしている部分。

現実のカードは勝手に数字やマークが変わったりはしないので、ここでも数字とマークはコンストラクタに渡して以降は値の取得のみできるようにした。 実際、spider で考えるなら変更する必要はないし、変えられるように拡張するのは簡単だし、とりあえずこのままでもいいのだが…

テストしているうちに、現在選択しているカードがどれか判った方が便利なことに気付いて、focus というメソッドを追加した。 true を渡すと背景を淡いピンクに、false を渡すと白にする。 わざわざ別に css を定義しているのに、ここだけコードの中で直接色指定…

テストしているうちに、表示とは関係無い汎用の値の設定/取得ができた方が便利なことに気付いて、set と get を追加した。 これらが扱う値は、内部では info という名前のハッシュとして保持する。 ま、実際これで便利になったのだが名前が…

addEventListener と removeEventListener は、イベントを全て cardMask で取り扱うためのもの。 普通のDOMイベントモデルと同じ引数を受け取って、内部で cardMask に設定し直す。 イベントリスナでは、イベント発生元つまり cardMask のプロパティ Card として、カードオブジェクトを取得する。 としてみたのは、上記の汎用の値をイベント処理の中で扱うためなのだが、このためにメモリの解放とかが微妙なことに…

定数をまとめたものの名前が CARD で、カードのコンストラクタの名前が Card って…