jQuery と CSS によるハノイの塔の実装

同カテゴリーの次の記事

インテル® Perceptual Computing SDK を使用した取り組みとフェイス・トラッキング

この記事は、インテル® デベロッパー・ゾーン(IDZ)サイトに掲載されている「Towers of Hanoi using jQuery and CSS」 の日本語参考訳です。


この記事はシンプルな「ハノイの塔」というゲームの設計と実装について解説します。このゲームでは、左側の軸にあるディスクを右側の軸に移す動作をアニメーションで描画します。プレーヤーは、アニメーションの速度とディスクの数を設定できます。このアプリケーションは移植性の高い HTML5 で記述され、主要なブラウザー上で動作します。このアプローチは、アプリケーションのハイブリッド化への対応を容易にします。例えば webview を使ったハイブリッド・アプリケーションがそれに該当します。

ソースコードはこちらで参照できます。
https://github.com/gomobile/sample-towers-of-hanoi

画面イメージ

要求事項:

  1. あらゆるスクリーン解像度に適合する
  2. 主要なブラウザーで動作する
  3. 容易にハイブリッド・アプリケーションに変換できる

設計における考慮:

すべての物体は長方形です。例えばディスク、軸、受け皿、コントロール・ボタンなどです。これらの長方形の幅と高さは、画面の余白の大きさに応じて柔軟に調整することができます。それらは HTML タグで記述されているので、そのサイズを動的に調整することができるのです。

ディスクの数は可変であり、動的にビジュアル要素を作成することはごく自然なことです。

jQuery は動的な HTML コンテントの作成に便利なフレームワークであり、.animate メソッドはページ上の物体を動かすことが出来るので、今回のアプリケーションの作成に適しています。また、jQuery では各ブラウザーの差異を抽象化し、ブラウザーに依存しないコードを書く手段としても用いられます。

仮想座標軸:

ページ上のビジュアル要素の位置を簡単に調整する仕組である、仮想座標軸について説明します。この考え方では、それぞれの要素の位置は「仮想座標」として指定されています。仮想座標のサイズは不変ですが、実サイズは伸縮します。この考え方については以下の図に表されています。


上の 2 つの図では、赤い矩形が 30×30 の仮想座標に配置されていますが、どちらの仮想座標上でも赤い矩形の大きさは 17×6 で、10,5 の位置に配置されています。これらの図では、仮想座標全体とその中に含まれるビジュアル要素が、スクリーンの大きさや縦横比に合わせて大きさが変化する様子が表現されています。

全体の構造:

サーフェス・オブジェクト(後述)の定義を除いたほぼすべての Javascript のコードは、jquery の .ready 関数の中に記述されます。すべての DOM エレメントの読み込みが完了した後、この関数が呼び出されます。それは以下のように準備されます。

$(document).ready (function () {
    ... // このコードは、DOM が読み込まれた後に起動されます
}

サーフェス・オブジェクト:

仮想座標に加えられるオブジェクトを管理するためのサーフェス・オブジェクトを作成します。これは仮想座標と実画面のマッピングを行います。コンストラクターの概要は以下に示します。

function Surface (jelem, width, height) {

    // サーフェスに要素を追加
    // jelem:  jQuery オブジェクトは DOM 要素を表現
    // x,y:    左上隅(仮想座標)
    // w,h:    要素の幅と高さ (仮想座標の)
    // return: 作成されたサーフェス要素を表現する整数

    this.Add_Elem = function (jelem, x, y, w, h) {...};

    // サーフェスから要素を削除
    // ielem: 削除する要素のインデックス。Add_Elem から返った値でなければならない

    this.Remove_Elem = function (ielem) {...};

    // 画面上の実際の画素位置に仮想座標のスケーリングを設定
    // pwidth,pheight:  仮想座標にマッピングするサーフェスの幅と高さ

    this.Set_Scale = function (pwidth, pheight) {...};

    // 現在のスケーリングに基づいてサーフェスに追加されるすべての要素の位置

    this.Position_All = function () {...};

    // 現在の位置から新しい位置へ要素を移動
    // to_x, to_y:  要素の移動先の仮想座標
    // callback:    移動するときに呼び出される関数。動きがアニメーションされて
    //                いる時に関連する場合発生しない

    this.Move_Elem = function (ielem, to_x, to_y, callback) {...};

    // 移動アニメーションの再生時間を変更
    // offset:  時間をミリ秒(ms)で設定。初期値は 300ms

    this.Change_Duration = function (offset) {...};
}

サーフェス・オブジェクトのインスタンスは1つだけ存在し、これは HTML ドキュメントの body 内の DOM 要素にマッピングされます。他のすべての DOM 要素は動的に作成されます。

    ...
    // 仮想座標系を保持する DOM 要素に対応した jQuery オブジェクトを作成
    var jsurface = $("#surface");

    // サーフェス・オブジェクトを作成。ここでは 30x30 仮想座標を設定
    var surface  = new Surface (jsurface, 30, 30);
    ...

<BODY>
<DIV id="surface"></DIV>
</BODY>

まず 30×30 のサイズの座標を用意します。位置は小数点を含んだ値を持つことができ、その粒度と要素の位置に制限はありません。

軸とボタンの作成:

軸とコントロール・ボタンはこの関数で作成されます。

function Create_Pins () {
     // 軸とプレートに対応する jQuery オブジェクトを作成
     var jpin1 = $("<DIV></DIV>").addClass ("pin").appendTo (jsurface);
     var jpin2 = $("<DIV></DIV>").addClass ("pin").appendTo (jsurface);
     var jpin3 = $("<DIV></DIV>").addClass ("pin").appendTo (jsurface);
     var jplate1 = $("<DIV></DIV>").addClass ("plate").appendTo (jsurface);
     var jplate2 = $("<DIV></DIV>").addClass ("plate").appendTo (jsurface);
     var jplate3 = $("<DIV></DIV>").addClass ("plate").appendTo (jsurface);

     // サーフェスに軸を追加
     var pin1 = surface.Add_Elem (jpin1, 4.5,  5, 1, 25);
     var pin2 = surface.Add_Elem (jpin2, 14.5, 5, 1, 25);
     var pin3 = surface.Add_Elem (jpin3, 24.5, 5, 1, 25);
     var plate1 = surface.Add_Elem (jplate1, 1, 29, 8, 1);
     var plate2 = surface.Add_Elem (jplate2, 11, 29, 8, 1);
     var plate3 = surface.Add_Elem (jplate3, 21, 29, 8, 1);
 }
 // コントロールを作成
 function Create_Control (img, handler) {
     var control = $("<img>");
     control.get(0).src = img;
     control.addClass ("control").appendTo (jsurface).click (handler);
     return control;
 }
 function Create_Controls () {
     surface.Add_Elem (Create_Control ("images/Image_PlayStart.png", Reset), 1.5, 27.5, 1.0, 1.0);
     surface.Add_Elem (Create_Control ("images/Image_Play.png", Start), 4.5, 27.5, 1.0, 1.0);
     surface.Add_Elem (Create_Control ("images/Image_Stop.png", Stop), 7.5, 27.5, 1.0, 1.0);
     surface.Add_Elem (Create_Control ("images/Image_Slow.png",Slower ), 13.5, 27.5, 1.0, 1.0);
     surface.Add_Elem (Create_Control ("images/Image_Fast.png", Faster), 16.5, 27.5, 1.0, 1.0);
     surface.Add_Elem (Create_Control ("images/Image_AddDisk.png", Add1), 23.5, 27.5, 1.0, 1.0);
     surface.Add_Elem (Create_Control ("images/Image_RemoveDisk.png", Sub1), 26.5, 27.5, 1.0, 1.0);
 }

軸や受け皿を構成する jQuery のオブジェクトは、jQuery ($) 関数で、addClass メソッドでクラスを追加される <DIV> 要素を作成することにより作成されます。.appendTo 関数は、新規作成された DOM 要素を jsurface 要素に追加するのに使用されます。それぞれの jQuery 呼び出しは、変更されたオブジェクトを表す jQuery オブジェクトを返します。この天才的ひらめきは、とても便利なパターンの連鎖を可能にします。

それぞれの受け皿の幅は 8 で、2 の間隔が空けられます。左右の受け皿は、画面の端から1 の間隔が空けられます。それぞれの軸の幅は 1 で、高さは 25 で、受け皿の中央から画面上部から 5 の間隔を空けた位置に配置されます。これは魔法のような仮想座標を使うことによって解決されます。

コントロール・ボタンは、HTML の <INPUT> エレメントによって作成されます。クラスを追加してボタンのスタイルを設定するだけでなく、クリックをハンドルする関数も用意します。これはJavascript の関数によってクリックされた時の処理を定義します。関数は、シンプルにユーザーの望む値に変更するための処理を行います。

ディスクの描画と消去:

ディスクは以下の関数により描画されます。

   function Create_Discs (disc_count) {
       var max_width  = 7;
       var min_width  = 3;
       var width_step = (max_width - min_width)/(disc_count - 1);
       var x_step     = width_step/2;
var height     = 2.6;  //20/disc_count; 
////(total_discs_height/disc_count > max_height) ? max_height : total_discs_height/disc_count;
	var width      = max_width;
       var x          = 1.5;
       var y          = 24.5- height;
       var discs = new Array ();
       for (var i = 0; i < disc_count; ++i) {
           var disc = $("

").addClass ("oval"); 
//.css('background-color', colors [i]);
           disc.appendTo (jsurface);
           discs.push (surface.Add_Elem (disc, x, y, width, height));
           x = x + x_step;
           width = width - width_step;
           y = y - height/1.5;
       }
       return discs;
   }

ユーザーによって選択できるディスクの数には、disc_count パラメーターが使用されます。ディスクの幅は最小が 3 で、最大が 7 です。ディスクの高さは演算によって求められ、積み重ねられたディスクの高さは 20 になります。ディスクの高さを固定値にすることも考えられます。上記のコードに多少の変更を加えることは容易です。そして CSS によって色は配列データになっています。これによりディスクは異なった色で描かれます。

ディスクの数は可変であるので、新しいディスクのためのスペースを作るために、既存のディスクを取り除く必要があるかもしれません。これは以下のように行われます。

function Remove_Discs () {
     if (typeof discs !== 'undefined') {
         for (var i = 0; i < discs.length; ++i) {
             var elem = surface.Get_Elem (discs [i]);
             elem.jelem.remove();
             surface.Remove_Elem (discs [i]);
         }
         delete discs;
     }
 }

作成されたディスクはディスク配列に格納されます。サーフェスから取り除かれる時のため、このようにサーフェスへのハンドルを用意します。この場合、それぞれのディスクは DOM とサーフェスの両方から取り除く必要があります。DOM から取り除くときは jQuery の .remove() メソッドを使用し、サーフェスから取り除くときは .remove_Elem() メソッドを使用します。

ウィンドウサイズ変更への対応:

ウィンドウサイズが変更されたとき、仮想座標と実座標のマッピングもサイズの変更に合わせて対応させる必要があります。これは jQuery のリサイズイベントのハンドラーを用意することで対応できます。

  function Resize (width, height) {
	jsurface.width (width);
	jsurface.height (height);
	surface.Set_Scale (width, height);
   	surface.Position_All ();
  }

  function Resize_To_Window () {
      	Resize ($(window).width(), $(window).height());
  }
  ...
      // サイズと位置の初期化
      Resize_To_Window ();

      // ウィンドウがリサイズされた場合に呼び出されるハンドラーの設定
      $(window).resize (Resize_To_Window);

Resize_To_Window() 関数は、jQuery の .width と .height 関数を使ってウィンドウのピクセルサイズを求めます。次にこれらの値から DOM 要素のピクセルサイズを設定します。Set_Scale() 関数で拡大率をセットした後に、サーフェス関数は Position_All() を呼び出して拡大率と位置の再設定を行います。Resize_To_Window() 関数はjQueryの .resize 関数でウィンドウが変更されたときに呼び出されます。

アニメーションの流れ:

ディスクをある軸から別の軸へ動かすためのコードを以下に示します

function Move_Disc (from_pin, to_pin, callback) {
    var from_pin_discs = pins [from_pin];
    var from_top_disc  = pins [from_pin].pop ();
    var x_move         = (to_pin - from_pin)*10;
    var elem           = surface.Get_Elem (from_top_disc);

    pins [to_pin].push (from_top_disc);
    surface.Move_Elem (from_top_disc, elem.x, 5-elem.h/1.5);
    surface.Move_Elem (from_top_disc, elem.x + x_move, 5 - elem.h/1.5);
    surface.Move_Elem (from_top_disc, elem.x, 23.5 - elem.h/1.5*(pins[to_pin].length), callback);
}

3 つの軸は0、1、2 で指定され、それぞれの軸ごとにディスクの現在位置を示す配列があります。

ディスクの動きは、次の 3 種類があります。

  • 軸の上端への動き
  • 軸と軸の間の動き
  • 軸の上端から下方向への動き

移動量は、軸の位置とピンに積み重ねられたディスクの枚数から求められます。

このコールバック・パラメーターは、移動の完了を呼び出し元に通知するためのものです。アニメーションがあるため、それらは即座には実行されません。

単一の要素が連続してアニメーション動作をする場合、jQuery はこれを問題なく処理することができます。しかし、複数要素が連続してアニメーション動作を行う場合、手動で対処する必要があります。

連続的に並べられた動作をご紹介します。

function Move_Disc_Queue (from_pin, to_pin) {
    queue.push ({from:from_pin, to:to_pin});
}
function Process_Queue () {
    if (queue.length > 0 && state == 'running') {
        var elem = queue.shift ();
        Move_Disc (elem.from, elem.to, Process_Queue);
    }
}
function Move_Stack (size, from, to, middle) {
    if (size == 1) {
        Move_Disc_Queue (from, to);
    }
    else {
        Move_Stack (size-1, from, middle, to);
        Move_Disc_Queue (from, to);
        Move_Stack (size-1, middle, to, from);
    }
}

Move_Disc_Queue () を呼び出す毎に、配列キュー内のキューの動きを再構築します。ここで皆さんは、ハノイの塔のアルゴリズムの標準的な再帰実装に気が付いているでしょう。キューが処理できる状態になると、Process_Queue() が呼び出されます。これにより、キューの処理が開始されます。現在のアニメーションが終了すると、それによって移動する次のディスクを処理するため、Process_Queue がコールバックとして用意されていることに注意してください。また、継続するキューを処理するため、変数の状態 ‘state’ を ‘running’ に設定しなければならないことに注意してください。これにより、ユーザーは任意の時点でアニメーションを一時停止することができます。

一般的な配列処理である .push と .shift は、終端への追加と、先頭の削除のために用意されています。

入力に対する応答:

コントロール・ボタンのクリックに対する処理を、以下に示します。

function Start () {
    if (state == 'stopped') {
        state = 'running';
        Process_Queue ();  // 再実行するキューを取得
    }
}
function Stop () {
    state = 'stopped';
}
function Reset () {
    if (state == 'stopped') {
        Remove_Discs ();
        discs    = Create_Discs (number_of_discs);
        pins [0] = discs.slice (0);  // ディスク配列の複製を作成
        pins [1] = new Array ();
        pins [2] = new Array ();
        Resize_To_Window ();
        delete queue;
        queue = new Array ();
        Move_Stack (pins [0].length, 0, 2, 1);  // 移動キューを埋める
    }
}
function Faster () {
    surface.Change_Duration (-50);
}
function Slower () {
    surface.Change_Duration (50);
}
function Add1 () {
    if (state == 'stopped') {
        if (number_of_discs < 10) {
            number_of_discs += 1;
        }
        Reset ();
    }
}
function Sub1 () {
    if (state == 'stopped') {
        if (number_of_discs > 2) {
            number_of_discs -= 1;
        }
        Reset ();
    }
}

ここで着目したいのは、state 変数の使い方です。この変数は、2 つの状態 stopped とrunning を保持します。ディスク枚数の追加や削減は、単に stopped の状態が変更されるに過ぎません。Reset 関数は、すべてのディスクを取り除き、スタート地点の軸に設定されている枚数のディスクを作成します。このアニメーションの初期値は、301ms に設定され、加速か減速ボタンが押されるたびに 50ms 分だけ値を変更します。最小の値は 1ms であり、それは JavaScript が扱える最小値でもあります。

見栄えを良くするために:

最後に、CSS のコードを追加して境界線とスクロールバーを取り除き、見栄えの良いアプリに仕上げます。

この CSS コードは、スクロールバーを取り除き、境界線を引きます。

HTML {
    overflow:hidden;
}
BODY {
    margin:0;
}

軸、受け皿、ディスク、コントロール・ボタンの CSS は、ほぼ同じです。

.pin
{
    position:absolute;
    border-radius:20px;
    background: #ab733a; 	// 古いブラウザー
    background: -moz-linear-gradient(left, #ab733a 1%, #d1a578 30%, #ab733a 53%, #d1a578 75%, #ab733a 100%); // FF3.6+
    background: -webkit-gradient(linear, left top, right top, color-stop(1%,#ab733a), color-stop(30%,#d1a578), color-stop(53%,#ab733a), color-stop(75%,#d1a578), color-stop(100%,#ab733a)); // Chrome,Safari4+
    background: -webkit-linear-gradient(left, #ab733a 1%,#d1a578 30%,#ab733a 53%,#d1a578 75%,#ab733a 100%); // Chrome10+,Safari5.1+
    background: -o-linear-gradient(left, #ab733a 1%,#d1a578 30%,#ab733a 53%,#d1a578 75%,#ab733a 100%); // Opera 11.10+
    background: -ms-linear-gradient(left, #ab733a 1%,#d1a578 30%,#ab733a 53%,#d1a578 75%,#ab733a 100%); // IE10+
    background: linear-gradient(to right, #ab733a 1%,#d1a578 30%,#ab733a 53%,#d1a578 75%,#ab733a 100%); // W3C
    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ab733a', endColorstr='#ab733a',GradientType=1 ); // IE6-9

    -webkit-box-shadow: 	0 rgba(0,0,0,0.2) .5em 5px;	// drop shadow
    -moz-box-shadow:  0 rgba(0,0,0,0.2) .5em 5px;	// drop shadow
    box-shadow:  0 rgba(0,0,0,0.3) .5em 5px;	// drop shadow
}
.plate {
	position: 	absolute;
	border-top: 	none;
	border-bottom: none; //1em double inherit;

	-webkit-border-radius: 	7em / 1.5em;
	-moz-border-radius: 	7em / 1.5em;
	border-radius: 		7em / 1.5em;

	color: 		hsl(25, 53%, 83%) !important;
	background-color: 	hsl(25, 53%, 83%);

	-webkit-box-shadow: inset rgba(255,254,255,0.6) 0 0.3em .3em, inset rgba(0,0,0,0.15) 0 -0.1em .3em, // inner shadow
	hsl(25, 53%, 83%) 0 .1em 3px, hsl(25, 37%, 83%) 0 .3em 1px, // color border
	rgba(0,0,0,0.2) 0 .5em 5px;	// drop shadow
	-moz-box-shadow: 	inset rgba(255,254,255,0.6) 0 0.3em .3em, inset rgba(0,0,0,0.15) 0 -0.1em .3em, // inner shadow
	hsl(25, 53%, 83%) 0 .1em 3px, hsl(25, 37%, 83%) 0 .3em 1px, // color border
	rgba(0,0,0,0.2) 0 .5em 5px;	// drop shadow
	box-shadow: 		inset rgba(255,254,255,0.7) 0.3em 0.4em .4em,  //inner shadow
 inset rgba(171, 115, 58, 1.0) 0 -3.1em 0,
   // color border
	rgba(0,0,0,0.3) 0 .5em 5px;	// drop shadow
}

.disc {
    position:absolute;
    border-radius:20px;
    -moz-box-shadow: 8px 0px 8px #333;
    -webkit-box-shadow: 8px 0px 8px #333;
    box-shadow: 8px 0px 8px #333;
    height: 50px;
    width: 100px;
    display:block;

    -moz-border-radius:75px;
    -webkit-border-radius: 75px;
    border-radius: 75px;
}
.oval {
 position:absolute;
	border-top: 			none;
	border-bottom: 			none; //1em double inherit;

	-webkit-border-radius: 	7em / 1.5em;
	-moz-border-radius: 	7em / 1.5em;
	border-radius: 			7em / 1.5em;

	color: 				hsl(55, 100%, 40%) !important;
	background-color: 	rgba(204, 78, 0, 0.5); //(hsl(55, 100%, 40%);

	-webkit-box-shadow: inset rgba(255,254,255,0.6) 0 0.3em .3em, inset rgba(0,0,0,0.15) 0 -0.1em .3em, // inner shadow
 hsl(188, 100%, 20%) 0 .1em 3px, hsl(188, 100%, 20%) 0 .3em 1px, // color border
	rgba(0,0,0,0.2) 0 .5em 5px;	// drop shadow
	-moz-box-shadow: 	inset rgba(255,254,255,0.6) 0 0.3em .3em, inset rgba(0,0,0,0.15) 0 -0.1em .3em, // inner shadow
	hsl(188, 100%, 20%) 0 .1em 3px, hsl(188, 100%, 20%) 0 .3em 1px, // color border
	rgba(0,0,0,0.2) 0 .5em 5px;	// drop shadow
	box-shadow: 		inset rgba(255,254,255,0.7) 0.3em 0.4em .4em,  //inner shadow
	inset rgba(204, 78, 0, 1.0) 0 -3.1em 0,
 // color border
	rgba(0,0,0,0.3) 0 .5em 5px;   // drop shadow

}

.control {
 position:absolute;
	text-decoration: 		none;
	font: 		24px/1em 'Droid Sans', sans-serif;
	font-weight: 		bold;
	text-shadow: 		rgba(255,255,255,.5) 0 1px 0;
	-webkit-user-select: 	none;
	-moz-user-select: 		none;
	user-select: 		none;

	padding: 			.5em .6em .4em .6em;
	margin: 			.5em;
	display: 			inline-block;

	-webkit-border-radius: 	8px;
	-moz-border-radius: 	8px;
	border-radius: 		8px;

	border-top: 		1px solid rgba(255,255,255,0.8);
	border-bottom: 		1px solid rgba(0,0,0,0.1);

	background-image: 	-webkit-gradient(radial, 50% 0, 100, 50% 0, 0, from( rgba(255,255,255,0) ), to( rgba(255,255,255,0.7) ));
	background-image: 	-moz-radial-gradient(top, ellipse cover, rgba(255,255,255,0.7) 0%, rgba(255,255,255,0) 100%);
	background-image: 	gradient(radial, 50% 0, 100, 50% 0, 0, from( rgba(255,255,255,0) ), to( rgba(255,255,255,0.7) ));

	-webkit-transition: background .2s ease-in-out;
	-moz-transition: 	background .2s ease-in-out;
	transition: 		background .2s ease-in-out;

	background-color: 	hsl(61, 100%, 20%);

	-webkit-box-shadow: inset rgba(255,254,255,0.6) 0 0.3em .3em, inset rgba(0,0,0,0.15) 0 -0.1em .3em, // inner shadow
	hsl(61, 100%, 67%) 0 .1em 3px, hsl(61, 50%, 30%) 0 .3em 1px, // color border
	rgba(0,0,0,0.2) 0 .5em 5px;	// drop shadow
	-moz-box-shadow: 	inset rgba(255,254,255,0.6) 0 0.3em .3em, inset rgba(0,0,0,0.15) 0 -0.1em .3em, // inner shadow
	hsl(61, 100%, 67%) 0 .1em 3px, hsl(61, 50%, 30%) 0 .3em 1px, // color border
	rgba(0,0,0,0.2) 0 .5em 5px;	// drop shadow
	box-shadow: 		inset rgba(255,254,255,0.6) 0 0.3em .3em, inset rgba(0,0,0,0.15) 0 -0.1em .3em, // inner shadow
	hsl(61, 100%, 67%) 0 .1em 3px, hsl(61, 50%, 30%) 0 .3em 1px, // color border
	rgba(0,0,0,0.2) 0 .5em 5px;	// drop shadow
}

.control:hover {
 background-color: hsl(61, 100%, 37%);}

.control:active {
	background-image: 	-webkit-gradient(radial, 50% 0, 100, 50% 0, 0, from( rgba(255,255,255,0) ), to( rgba(255,255,255,0) ));
	background-image: 	-moz-gradient(radial, 50% 0, 100, 50% 0, 0, from( rgba(255,255,255,0) ), to( rgba(255,255,255,0) ));
	background-image: 	gradient(radial, 50% 0, 100, 50% 0, 0, from( rgba(255,255,255,0) ), to( rgba(255,255,255,0) ));

	-webkit-box-shadow: inset rgba(255,255,255,0.6) 0 0.3em .3em, inset rgba(0,0,0,0.2) 0 -0.1em .3em, // inner shadow
							rgba(0,0,0,0.4) 0 .1em 1px, // border
							rgba(0,0,0,0.2) 0 .2em 6px; // drop shadow
	-moz-box-shadow: 	inset rgba(255,255,255,0.6) 0 0.3em .3em, inset rgba(0,0,0,0.2) 0 -0.1em .3em, // inner shadow
							rgba(0,0,0,0.4) 0 .1em 1px, // border
							rgba(0,0,0,0.2) 0 .2em 6px; // drop shadow
	box-shadow: 		inset rgba(255,255,255,0.6) 0 0.3em .3em, inset rgba(0,0,0,0.2) 0 -0.1em .3em, // inner shadow
							rgba(0,0,0,0.4) 0 .1em 1px, // border
							rgba(0,0,0,0.2) 0 .2em 6px; // drop shadow

	-webkit-transform: 	translateY(.2em);
	-moz-transform: 	translateY(.2em);
	transform: 			translateY(.2em);
}
.button_down {
    box-shadow: -3px -3px 3px #777;
    -moz-box-shadow: -3px -3px 3px #777;
    -webkit-box-shadow: -3px -3px 3px #777;
}

最も重要なこととして、’position:absolute’ があります。もし位置指定に絶対位置指定を選択しないと、仮想座標が正しく動作しません。

軸、受け皿、ディスクの表現に 3D 効果を出すため、陰影を追加します。ディスクと軸は角を丸めます。

他に考慮すべきこと:

  1. ウィンドウサイズを変更しても、コントロール・ボタン上の文字のフォントサイズが変更されない。修正には 2 つの方法が考えられる。コントロール・ボタン上の文字をイメージ画像にするか、Position_All() 関数にフォントサイズ変更の機能を加えることである。
  2. 画面が回転したときに、ウィンドウサイズ変更のイベントが発生しないことがある。これを修正するために、画面が回転したときに Resize_To_Window() 関数が呼び出されるようにする。
  3. コントロール・ボタンを素早くクリックすると、ブラウザーによってはダブルクリックによるズームと判断される。これを避けるためにズーム機能を無効にする。試してはいないが、これは META タグで可能と思われる。
  4. モバイルブラウザーの場合、ブラウジングのコントロール・ボタン(戻る、履歴、など)を非表示にすることができる。これにより画面上のアプリが使えるスペースが広がる。
  5. IE ブラウザー用に、丸めたコーナーと陰影の表現を追加する。
  6. 3D 効果を出すためにディスクにグラデーションを加える。これにより丸い感じが表現出来る。グラデーションはブラウザーによってその効果が変わってくるので、ブラウザーの違いを学ぶのに良い練習になる。

Windows 8 Store App への移植

このサンプルコードは、Windows 8* Store App へ移植することができます。詳細については、「Porting HTML5 Samples to Windows 8* Store」を参照ください。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

関連記事