JavaScript入門 イベント -イベント伝播の停止-
他の要素への伝播を停止(stopPropagation)
stopPropagationメソッドを呼び出すことで、「他の要素への伝播を止めます」。キャプチャリングフェーズで呼び出された場合には、下位の要素に設定されているアクションが実行されることはありません。
ターゲットフェーズまたはバブリングフェーズで呼びされた場合には、上位の要素にイベントを伝播しません。
例:キャプチャリングフェーズでイベント伝播を停止した場合
<body>
<section>
section<br>
<div>
div<br>
<button>button</button> //クリックイベント
</div>
<p>p<br>この要素は伝播の対象外です。</p>
</section>
<style>
section, div, p , button{
padding: 10px;
border: 10px solid skyblue;
}
</style>
<script>
const button = document.querySelector( "button" );
const div = document.querySelector( "div" );
const section = document.querySelector( "section" );
//buttonに対するアクションを登録
button.addEventListener( "click", ( event ) => {
console.log( "buttonのclickイベントが実行されました。" );
} );
//divに対するアクションを登録
div.addEventListener( "click", ( event ) => {
console.log( "divのclickイベントが実行されました。" );
} );
//sectionに対するアクションを登録
section.addEventListener( "click", ( event ) => {
event.stopPropagation(); //イベント伝播を停止
console.log( "sectionのclickイベントが実行されました。" );
},{ capture: true } ); //キャプチャリングフェーズでアクションを実行
</Script>
</body>
上記コードでは、<section>のclickイベントが発火したときにstopPropagationが実行されます。また、<section>のイベントリスナには、{ capture: true }が設定されています。そのため、<buton>に登録したアクションは実行されません。なお、stopPropagationが呼びされた要素に複数のアクションが設定された場合には、それらのアクションはすべて実行されます。
アクションを即時で停止(stopImmediatePropagation)
同じ要素に複数のアクションが登録されていた場合に、後続のアクションへの伝播を止めたい時には、stopImmediatePropagationを使います。stopPropagationでは他の要素に伝播するのを防ぎますが、stopImmediatePropagationでは「実行中のアクションが完了するとイベント伝播は即時で終了します」。そのため、後続のキャプチャリングやバブリングも停止します。
例:Event.stopImmediatePropagation
<body>
<div>この要素の色が変わります。</div>
<button id="all-color">文字色と背景色の変更</button>
<script>
const targetEl = document.querySelector( "div" );
const allColorBth = document.querySelector( "#all-color" );
//文字色を変更するアクション
function colorChange( event ){
event.stopImmediatePropagation(); //後続のアクションへのイベント伝播を停止
targetEl.style.color = "#ff0000"; //文字色を赤色に変更
}
//背景色を変更するアクション
function bgColorChange( event ){
targetEl.style.color = "blue"; //背景色を青色に変更
}
//関数を2つ登録可能
//アクションは登録された順番(colorChange→bgColorChange)で実行される。
allColorBth.addEventListener( "click", colorChange );
allColorBth.addEventListener( "click", bgColorChange );
</script>
</body>
上記コードでは、#all-color要素に対して、colorChangeとbgColorChangeアクションを追加していますが、colorChangeの中でstopImmediatePropagationを実行しているため、後続のbgColorChangeは実行されません。
ブラウザのデフォルト処理を止める場合(preventDefault)
HTMLタグによっては、ブラウザの特定の処理が実装されている場合があります。たとえば、アンカータグ(a)はリンクを作成するときに使いますが、このタグがクリックされた場合にはブラウザのデフォルト処理によって、リンク先URLに画面が遷移します。また、<input type=”submit”>などは、クリックしたタイミングで、フォームに設定されているリンク先に対してリクエストを送信します。これらのブラウザのデフォルト処理を止めるには、preventDefaultをアクション内で実行します。
例:すべてのリンクの機能を無効化する
<body>
<a href="https://google.com/" >googleには遷移しません。</a>
<a href="https://yahoo.co.jp/" >Yahooには遷移しません。</a>
<script>
//すべてのアンカータグを取得
const links = document.querySelectorAll( "a" );
links.forEach( link =>
link.addEventListener("click", event => {
event.preventDefault();
alert( event.currentTarget.textContent );
})
);
</script>
</body>
なお、preventDefaultでは、イベント伝播は止まりません。そのため、キャプチャリングやバブリングも止めたい場合には、stopPropagationやstopImmediatePropagationとあわせて実行します。
targetとcurrentTargetの違い
イベントが発生した要素を取得するプロパティであるtargetとcurrentTargetの違いについて解説します。EventオブジェクトのtargetとcurrentTargetには、異なる要素が格納される場合があります。これは、バブリングの作用によって起こります。
例:targetとcurrentTargetが異なる場合
<body>
<section id="container">
<button>button</button> <!-- イベントはここから発生する-->
</section>
<script>
// #container( sectionタグにアクションを登録する )
const containerEl = document.querySelector( "#container" );
containerEl.addEventListener( "click", function( event ) {
console.log( `${ event.currentTarget.nodeName }` ); //currentTargetは常にアクションを登録した要素が格納される
console.log( `${ event.target.nodeName }` ); //targetはイベントが発生した要素が格納される
});
</Script>
</body>
上記コードでbuttonタグをクリックすると、buttonタグでclickイベントが発生します。このとき、targetとcurrentTargetでは、異なる要素が取得されます。「currentTargetの場合は常に実行中のアクションが登録された要素が取得されますが、targetの場合はイベントが発生した要素が取得されます。」
イベントの補足事項
パッシブリスナ
パッシブリスナ(Passive Listener)とは、Chrome51より導入されたスクロールのパフォーマンスを改善するための仕組みです。addEventListenerのオプションに{ passive:true }を渡すことによって、パッシブリスナが有効になります。これにより、アクション内でEvent.preventDefault()の実行を無効化します。なお、Chromeの場合は、デフォルトが{ passive: true }です。
touchmoveイベントやwheelイベントに対してpreventDefaultを実行した場合は、スマホやPCでのスクロール操作を無効化できます。
- touchmove:スマホのタッチ操作が継続している間は連続的に発火する
- wheel:マウスのホィール(マウス上部にあるスクロール用装置)が回転している間は連続的に発火する
例:スクロール処理を禁止
<body>
<div></div>
<style>
body{
margin: 0;
}
div{
background: linear-gradient(#e66465,#9198e5);
height: 200vh;
}
</style>
<script>
function preventScroll( event ){
event.preventDefault();//スクロール処理を停止
}
document.addEventListener( "touchmove", preventScroll,{ passive: false } ); //touchmoveイベントのデフォルト処理(スクロール処理)を阻止
document.addEventListener( "wheel", preventScroll,{ passive: false } ); //wheelイベントのデフォルト処理(スクロール処理)を阻止
</Script>
</body>
上記コードのように、デフォルト処理としてスクロールが行われるイベントに登録したアクション内でpreventDefaultを実行すると、スクロール処理が無効化されます。しかし、「ブラウザは、アクションが実際に実行されるまではアクション内でpreventDefaultを実行されるのかを知るすべはありません。」そのため、touchmoveイベントやwheelイベントに対してアクションが登録されている場合には、アクション内のコードがすべて実行されて初めてEvent.preventDefaultが内部で呼び出されていないことがわかります。極端な例を挙げると、「アクション内に1000行分の処理がある場合、その最後の行までEvent.preventDefaultが実行されていないことを確認してから、画面上のスクロールを行う」ことになります。そのため、スクロールにアクションを登録した場合には、スクロールがガタついたり、スクロール時の画面の表示に遅れが出たりするという問題がありました。そこで考えられたのが{passive: true}オプションです。このオプションをaddEventListenerでアクションを登録するときに渡すことで、アクション内でのEvent.preventDefault()の実行を無効化します。これによってブラウザは、{passive: true}のアクションに関してはEvent.preventDefault()の処理が内部で使われていないことを担保できるため、アクションの実行を待たずに画面スクロール描写(デフォルト処理)を行うことができます。そのため、スクロール時のパフォーマンスが大きく改善されます。
イベントをスクリプトから実行
ユーザーの画面入力によって発生するclickイベントやInputイベントなどをJavaScriptコードから発火する方法。コードからイベントを発火させるには、dispatchEventメソッドを使います。
構文:dispatchEventの記法
let cancelled = EventTarget.dispatchEvent( event );
- EventTarget:イベントを発生させたい要素を設定します。Elementやwindow、documentなど。
- event:発火したいイベントタイプのEventオブジェクトを渡します。Eventオブジェクトは、new Event( “イベントタイプ” [, { bubbles: true } ] )の形式でインスタンス化します(第2引数 { bubbles: true }を省略した場合には、バブリングは発生しません)。
- cancelled:イベントで発火により実行されたアクションの中でEvent.preventDefaultが1回でも実行された場合にfalseが返ります。それ以外はtrueです。
clickイベントをコードから発火
<body>
<!-- ボタンがクリックされると1ずつカウントアップします。 -->
<span>0</span>
<!-- ボタンを非活性にします。 -->
<button disabled>+</button>
<script>
const button = document.querySelector( "button" );
const span = document.querySelector( "span" );
let count = 0;
button.addEventListener( "click", function(event){
count++;
span.textContent = count;
});
//Eventオブジェクトをインスタンス化
const myEvent = new Event( "click" );
//1秒間隔でclickイベントを発火
setInterval( () => {
button.dispatchEvent(myEvent);//イベント発火
}, 1000);
</Script>
</body>
上記の例では、<button>要素のclickイベントに登録されたアクションを、button.dispatchEvent( myEvent );によって1秒間隔で発火しています。それによって、画面上の数値が1秒間隔で1ずつカウントアップしていきます。また、buttonタグにdisabled属性を付けることによって、画面上からはクリックできない状態にしています。