JavaScriptイベントバブリングの実装と停止方法

1. イベントの基礎

JavaScriptにおけるエフェクトはイベントによってトリガーされます。イベントソースとは、HTMLの要素(ノード)のことを指し、複数のイベントを同一个要素に付与することが可能です。

主要なイベントの種類

  • キーボードイベント: keypress(キー押下中)、keyup(キーを離した時)、keydown(キーを押した時)
  • ドキュメントイベント: load(ページ読み込み完了)
  • フォームイベント: focus(フォーカス取得)、blur(フォーカス喪失)、submit(フォーム送信)、change(値変更)
  • その他のイベント: scroll(スクロール)、select start(テキスト選択開始)

2. Eventオブジェクトについて

イベントが発生すると、自動的にeventオブジェクトが生成されます。このオブジェクトには、イベントが発生した要素、キーボード按键の状態、マウスの位置、マウスの按钮状态などの情報が含まれます。イベントハンドラはイベントの発生と同時に実行されます。

eventオブジェクトのプロパティとメソッド

プロパティ/メソッド 説明
e.target イベントをトリガーした要素を返す(標準)
e.srcElement イベントをトリガーした要素を返す(IE6-8、非推奨)
e.type イベントタイプを返す(click、mouseoverなど、on除外)
e.returnValue デフォルトイベントを阻止(IE6-8用、リンク遷移阻止など)
e.preventDefault() デフォルトイベントを阻止(標準仕様)
e.stopPropagation() バブリングを阻止(標準仕様)
e.cancelBubble バブリングを阻止(IE仕様)

3. デフォルトイベントの阻止

aタグのデフォルト動作はページ遷移ですが、独自のリダイレクト処理を実装したい場合は、デフォルトイベントを阻止する必要があります。

<a id="navigation" href="https://example.com">リンク</a>
<script>
  var navElement = document.getElementById("navigation");
  navElement.onclick = function() {
      console.log("カスタム処理を実行"); 
      return false;
  }
</script>

デフォルトイベント阻止の2つの方法

要素によって付与方法が異なるため、阻止方法も異なります:

  • onclickプロパティまたはattachEvent()を使用する場合: return falseで阻止
  • addEventListener()を使用する場合: event.preventDefault()で阻止
<a id="navigation" href="https://example.com">リンク</a>
<script>
var navElement = document.getElementById("navigation");

// 方法1: onclickプロパティを使用
navElement.onclick = function() {
  console.log("方法1の処理"); 
  return false; 
}

// 方法2: addEventListenerを使用
navElement.addEventListener('click', function(evt){
    var eventData = evt || window.event;
    console.log('方法2の処理');
    eventData.preventDefault();
});
</script>

4. イベントフローとバブリング

バブリングは、水中の泡が水面に向かって上昇する動作に例えられます。泡が上昇する過程で異なる水深を通過するように、DOMツリーにおいてもイベントが子要素から親要素へ順に伝達されていきます。最後にDOMのルートノードまで到達し、子要素と親要素が同一个イベントを持つ場合、子要素がトリガーされると親要素も連続して反応します。

ブラウザ別のバブリング範囲

IE 6.0: div → body → html → document

その他のブラウザ: div → body → html → document → window

注意: blur、focus、load、unloadイベントにはバブリング特性がありません。

<style>
  #parentContainer{
    width: 300px;
    height: 300px;
    background-color: #f00;
    margin: auto;
  }
</style>

<body>
    <div id="parentContainer">
        <button id="actionButton">クリック</button>
    </div>

<script>
    window.onload = function(){
        var parentContainer = document.getElementById('parentContainer');
        var actionButton = document.getElementById('actionButton');
        
        parentContainer.onclick = function(){
            alert('親要素クリック');
        }
        
        actionButton.onclick = function(){
            alert('子要素クリック');
        }
        
        document.onclick = function(){
            alert('ドキュメントクリック');
        }
    }
</script>

バブリングを阻止する方法

ブラウザ互換性を考慮した実装:

if(event && event.stopPropagation){ 
    event.stopPropagation();
}else{
    event.cancelBubble = true;
}

バブリングを阻止したい場合、該当のイベントハンドラ内で上記コードを記述します。

5. addEventListenerでのバブリング制御

addEventListenerの3番目のパラメータで、イベントの伝達方向を変更できます。

<style>
    .container{
        width: 100%;
        height: 100%;
        background-color: #f00;
    }
    .wrapper{
        width: 300px;
        height: 300px;
        background-color: #0f0;
    }
    .box{
        width: 100px;
        height: 100px;
        background-color: #00f;
    }
</style>

<body>
    <div class="wrapper">
        <div class="box"></div>
    </div>
</body>

<script>
    var boxElement = document.querySelector('.box');
    var wrapperElement = document.querySelector('.wrapper');
    
    boxElement.addEventListener('click', handleBoxClick);
    function handleBoxClick(){
        console.log('boxがクリックされました');
    }

    wrapperElement.addEventListener('click', handleWrapperClick);
    function handleWrapperClick(){
        console.log('wrapperがクリックされました');
    }

    document.body.addEventListener('click', handleBodyClick);
    function handleBodyClick(){
        console.log('bodyがクリックされました');
    }
</script>

3番目のパラメータ(useCapture):

  • true: イベントキャプチャ段階で発火(親→子)
  • false: イベントバブリング段階で発火(子→親)- デフォルト値

タグ: javascript event-bubbling DOM-events event-handling frontend

8月4日 13:08 投稿