スポンサーリンク

JavaScriptの動かないコード (中級編) 不要なイベントが連鎖で発生してしまう  (バブリングの対処)



以下のJavaScriptコードが意図した動作をしないのは,なぜですか。(制限時間1分)


やりたい事:

  • 右図のような「クリックゲーム」を作る。
  • 赤い部分をクリックしたらゲームオーバー。
<div id="jirai"
 style="background-color:red;
 	 width:200px; height:200px;" >
地雷源

	<div id="goal"
	 style="background-color:green;
	 	 width:50px; height:50px;
	 	 position:relative; top:50px; left:50px;" >
	安全地帯
	</div>

</div>

<script language="JavaScript">

// クリック時のイベントを割り当てる
jirai.onclick = game_over;
goal.onclick = game_clear;


// 赤い部分をクリックした場合
function game_over()
{
	alert( "地雷を踏んでしまいました。ゲームオーバー" );
}


// 青い部分をクリックした場合
function game_clear()
{
	alert( "安全地帯に到着しました。ゲームクリア" );
}

</script>

発生する不具合


FirefoxでもIEでも,赤い部分をクリックすると,ちゃんとゲームオーバーになる。


ところが安全地帯をクリックすると,「ゲームクリア」のメッセージが表示された直後に,なんと「ゲームオーバー」のメッセージも続いて表示されてしまう。

不具合の原因

この原因は,DOMバブリングという仕様により,要素の「背面」に存在する要素のイベントが実行されてしまうため。

Javascript/イベントのバブリングという現象
http://yakinikunotare.boo.jp/orebase/...

バブリングとは・・・

イベント発生源(クリックされたりした要素)からの親ノード親ノードへとイベントが伝わること。

深い部分の要素から泡が浮き上がってくるようにイベントが伝播するからバブリング。



キャプチャリングとバブリング Firefox編
http://d.hatena.ne.jp/monjudoh/200804...

イベント伝播は3つの段階で構成されます。

1. キャプチャリングフェーズ
2. ターゲットノード自身でのイベントハンドラの実行
3. バブリングフェーズ


冒頭の例では,div タグが入れ子になっている。

そのため,子要素のクリックイベントを親要素もキャッチしてしまい,ゲームクリア時に親要素である地雷源の「ゲームオーバー」のイベントが発生してしまったのだ。

解決策

回避策としては,このバブリング現象を回避するために,ブラウザに専用のメソッドがちゃんと準備されているので,それを使えばよい。

<div id="jirai"
 style="background-color:red;
 	 width:200px; height:200px;" >
地雷源

	<div id="goal"
	 style="background-color:green;
	 	 width:50px; height:50px;
	 	 position:relative; top:50px; left:50px;" >
	安全地帯
	</div>

</div>

<script language="JavaScript">

// イベントを割り当てる
jirai.onclick = game_over;
goal.onclick = game_clear;


// 赤い部分をクリックした場合
function game_over()
{
	alert( "地雷を踏んでしまいました。ゲームオーバー" );
}


// 青い部分をクリックした場合
function game_clear()
{
	alert( "安全地帯に到着しました。ゲームクリア" );

	// クロスブラウザでイベントオブジェクトを取得
	var evt = arguments[0] || window.event;
	
	// イベントの伝搬を防止
	stopBubbling( evt ); 
}


function stopBubbling( e )
{
	// イベントオブジェクトがサポートしているメソッド名を判定

	if( e.stopPropagation )
	{
		e.stopPropagation(); // FF
	}
	else if( window.event )
	{
		window.event.cancelBubble = true; // IE
	}
}

</script>

FFなら stopPropagation(伝搬を止める意),IEなら cancelBubble を使う。

この場合,安全地帯をクリックすると「ゲームクリア」のイベントだけが発生する。




別の修正案として,テーブルの特定のセルだけを安全地帯とみなして,セルごとに異なるイベントを割り当てるという手もある。
そうすれば要素の親子関係によって混乱せずに済む。



冒頭のコードに遭遇した場合,「バブリング」という名前を知っていないと,どうやって対処したらいいのかわからず困るかもしれない。