明日もデザインで食べていこう![08]HTML+CSS+JSでカルーセル回転メニュー
── 秋葉秀樹 ──

投稿:  著者:


いまさらカルーセル回転......なんですけど、考えれば考えるほど奥が深いメニュー作り。今回は、いつもFlashでやってた、くるくる立体的に回るメニューのHTML版を作ってみたいと思います。

さてさて、まずは完成品を見てください。(IE以外で対応)
< http://akibahideki.com/digicre/case_08/
>

Flashっぽい立体回転ってやつですね。
まずはさっそくHTMLから、ソースコードはこんな感じ。

<body>
<ul>
<li><img src=images/pic.jpg alt>
<li><img src=images/pic.jpg alt>
<li><img src=images/pic.jpg alt>
<li><img src=images/pic.jpg alt>
<li><img src=images/pic.jpg alt>
<li><img src=images/pic.jpg alt>
<li><img src=images/pic.jpg alt>
<li><img src=images/pic.jpg alt>
<li><img src=images/pic.jpg alt>
<li><img src=images/pic.jpg alt>
<li><img src=images/pic.jpg alt>
<li><img src=images/pic.jpg alt>
</ul>
</body>

HTML5な書き方です。XHTMLな考え方と比べると違和感を感じますね。ソースはこれだけです、あとはCSSがどうなっているかというと

<style>
html, body, ul, li {
padding: 0;
margin: 0;
position: relative;
}

body {
background: black;
width: 100%;
height: 100%;
}

li {
list-style: none;
position: absolute;
}
</style>

以上です、単純ですね、とくにトリッキーな事もやってません。ではここでJavaScriptです。まずはどうやって円周上に<li>要素を配置するか、考えてみましょう。

【1】画面の幅、高さを取得する
【2】それぞれの<li>要素を、角度をずらしながらCSSのleft(横座標)を三角関数cos、top(縦座標)をsinで配置していく
【3】重なった時、一番下の<li>が上に来ないといけないので、z-indexをJavaScriptで入れ替えていく
【4】下の方が大きく、上に表示されている(topの値が少ない)<li>は小さくする。これはCSS3のtransform:scale()で可能。

さて、まずはここまでやってみることにします。



【1】画面の幅、高さを取得する

stageWidth = window.innerWidth;
stageHeight = window.innerHeight;

ここでは、画面の内側を取りましょう。
ひとまず関数の外に出しておいた変数stageWidthとstageHeightに代入します。

【2】それぞれの<li>要素を、角度をずらしながらCSSのleft(横座標)を三角関数cos、top(縦座標)をsinで配置していく。その前準備として、

for(var i=0; i<lists.length; i++ ){
photoDegree.push(i*360/lists.length);
}

これは、それぞれの<li>に対して1つずつ違った角度情報を与えたいので、それ用の配列を作っておきました。1番目の<li>は0度、次は30度、次は60度...といったように、i*360/lists.lengthというのは360度を<li>の総数分で割ってます。これで<Ii>の角度が等分出来たわけです。

さて、ここから以下のように作っていきます。

for (var i=0; i<lists.length; i++) {
var photo = lists[i];
var x = Math.cos(photoDegree[i]*Math.PI/180)*radius;
var y = Math.sin(photoDegree[i]*Math.PI/180)*radius;
photo.style.left = x+stageWidth/2+"px";
photo.style.top = y+stageHeight/2+"px";
}

これでまんまるの円周上に<li>が配置されたと思います。X座標(横座標)を決めるときはcos、Y座標を決めるときはsinとなります。「半径」と「中心からの角度」の2つが分かっていたら、XとYの座標が決定できます。

Math.PI/180というのは、角度をラジアン度に変換する公式です。sinとcosはラジアンで計算します。半径は変数radiusに設定しています。
photoDegree[i]によって角度がそれぞれ違い、今回の場合だと12枚なので、30度ずつずれて円周上に並びます。

【3】重なった時、一番下の<li>が上に来ないといけないので、z-indexを
JavaScriptで入れ替えていく

これは1行で実現。

photo.style.zIndex = Math.floor(Math.sin(photoDegree[i]*Math.PI/180)*radius);

JavaScriptでCSSを触るときは、プロパティ名にハイフンが入れられないので、ハイフンを省略して大文字で書きます。z-index → zIndexとなります。計算方法は、見ての通り、縦座標を取るときの公式を使っています。ただし、小数点をz-indexは受け入れてくれないので(当然か)、どんな方法でも良いので整数に直すことが必要です。これで重ね順が自然になりました。

【4】下の方が大きく、上に表示されている(topの値が少ない)<li>は小さくする、これはCSS3のtransform:scale()で可能。

いってみよう!

var scale = (y+stageHeight/2)/(stageHeight/2)*0.8;
photo.style.WebkitTransform = "scale("+scale+", "+scale+")";
photo.style.MozTransform = "scale("+scale+", "+scale+")";
photo.style.OTransform = "scale("+scale+", "+scale+")";

まあ、これが正しいというわけではないのですが、一例として。1つめの0.8は大きくなったり小さくなったりする度合いなので、変更して試してみてください。変数scaleで倍率を出しています。

(y+stageHeight/2)/(stageHeight/2)

なので画面の縦軸の中心から<li>要素の縦座標を足した位置から、画面の縦軸の中心を割るので、上にあるものは小さく、下にあるものは大きく計算されます。WebkitTransformは-webkit-transformという意味ですね。他は......もうお分かりですね。このサンプルはFirefox 4Beta、Safari、Chrome、Operaなどで動作が確認できています。

それにしても、
"scale("+scale+", "+scale+")"
って、、単純に文字列なんですね、なんか不思議。。

【まとめ】
マウスの位置によって横回転のスピードを変更します。

window.addEventListener("mousemove", getMousePoint, false);
function getMousePoint(e) {
mouseX = e.clientX;
mouseY = e.clientY;
}

マウスが動くたびにグローバル変数mouseX, mouseYが入れ直されます。
さらに

degree.x = (mouseX-(stageWidth/2))/100;
photoDegree[i]+=degree.x;

マウスが真ん中にいるとdegree.xの値が0に近くなり、画面端にいると数が極端に大きくなります(正、負の符号はつきますが)。よってこれを足しながら毎フレーム処理していくと、クルクル回っているように見えます。

最終的なJavaScriptコードはこちら。

<script>
window.addEventListener("load", init, false);
var stageWidth, stageHeight, radius=400;
var mouseX=0, mouseY=0;
var degree = {x:0, y:0};
var lists;
var photoDegree=[];

function init(e) {
lists = document.getElementsByTagName("li");
stageWidth = window.innerWidth;
stageHeight = window.innerHeight;

for(var i=0; i<lists.length; i++ ){
photoDegree.push(i*360/lists.length);
}

loop();
setInterval(loop, 1000/30);
window.addEventListener("mousemove", getMousePoint, false);
}

function loop () {
degree.x = (mouseX-(stageWidth/2))/100;
degree.y = (mouseY-(stageHeight/2));
for (var i=0; i<lists.length; i++) {
var photo = lists[i];
var img = lists[i].getElementsByTagName("img")[0];
var x = Math.cos(photoDegree[i]*Math.PI/180)*radius-img.width/2;
var y = Math.sin(photoDegree[i]*Math.PI/180)*(radius/2)-img.height/2-degree.y;
photo.style.left = x+stageWidth/2+"px";
photo.style.top = y+stageHeight/2+"px";
photo.style.zIndex = Math.floor(Math.sin(photoDegree[i]*Math.PI/180)*radius);
var scale = (y+stageHeight/2)/(stageHeight/2)*0.8;
photo.style.WebkitTransform = "scale("+scale+", "+scale+")";
photo.style.MozTransform = "scale("+scale+", "+scale+")";
photo.style.OTransform = "scale("+scale+", "+scale+")";
photoDegree[i]+=degree.x;
}
}

function getMousePoint(e) {
mouseX = e.clientX;
mouseY = e.clientY;
}
</script>

【あきば・ひでき】hidetaro7@gmail.com
< http://www.akibahideki.com/blog/
>

前回のHTML5 Videoコーデックのお話の中で、Macの主要ブラウザ全てにTheora Oggが対応してると書いたんですが、あれは僕のマシンにOggが再生出来るプラグインをインストールしていたことが原因で、Safariのみ、Theora Oggに対応していません。プラグインのインストールにより、MacのSafari含めた主要ブラウザすべてでOggコーデックが再生されます。

詳しくは記事を書きました。読者さんを惑わせてしまい申し訳ないです!
あと色々と技術的なご指摘をいただきました古籏さんには大変感謝しています。
< http://www.akibahideki.com/blog/htmlcss/safariogghtml5-video.html
>

今年は色んな地域で講演に呼んでいただけた年だったなと思います。早いもので、今年ももう終わりです。そうそう、来年は早速11月まで出張が決まってしまいました......またバタバタしそうですが、こんな私、どうぞ来年もよろしくお願いします!!

テクニカルディレクター・デザイナー。DTP黎明期からグラフィックデザインを学び、東京都営団地下鉄など交通広告を多数手がける。同時に音楽活動も活発に行い、西日本半全国ツアーなどを展開、某専門学校のテーマソングを作詞作曲、編曲から楽器全てを演奏してレコーディングするなど、マルチなクリエイティブ活動も。最近では東京と大阪の教育施設などで講師業をも務める。HTML CSS JavaScript Flash ActionScript 3DCG Movie DTP GraphicDesign...多種スキルを持つ。Web標準技術だけに執着せず、全てのメディアで説得力のある表現にチカラを注ぎたい、そんな仕事をしたい。