mouseenterとmouseoverの違いなどDOMイベントの発生状況を可視化して調べてみたよ
HTML5のDOMイベントに、mouseenter
と mouseover
という、よく似たマウスイベントがあります。ここには、その違いについて調べたことを書いておきます。
どちらもマウスポインターが要素の上に入ってきた時に発生するイベントで、それぞれに対応する「マウスポインタ―が要素から外れた」時のイベントとして mouseleave
と mouseout
もありますね(mouseenter
には mouseleave
、mouseover
には mouseout
が対応します)。
この2種類のイベント間には、発生要因や伝播(バブリング/プロパゲーション)に関する違いがあります。
私は最近まで、この違いを意識しておらず「歴史的理由による別名?」かと思っていて、その場で適当に思いついた方を使っていました。 先日ふと疑問に思ってMDNで調べてみたら、どうやら上記のように明確な違いがあると知ったのですが、はっきりイメージがつかみきれなかったので確認用に作ってみたのが以下のものです。
マウスイベントを可視化する
以下、4つのDOM要素が入れ子になっていて、各要素が拾ったマウスイベントを表示します(前述の4つのイベントだけ調べるつもりでしたが、せっかくなので他のイベントもListenしました)。要素の上でマウスを動かせば各インジケータがビカビカします(ちょっとうるさいですが目立つと思って)。
各要素には、preventDefault
とstopPropagation
というチェックボックスがあります。
それぞれチェックを付けると、その要素のイベント処理で event.preventDefault()
やevent.stopPropagation()
を実行します。
例えば、‘#Div4‘の上でマウスを動かすとmousemoveが全要素に発生しますが、stopPropagation
にチェックを入れた要素の親へは伝播しません
(preventDefault
はこれらのイベントに関して変化がなさそうなのですが、とりあえず残しています)。
マウスポインタが要素の「外側から内側」へ移動するとき
全てのstopPropagation
のチェックを外した状態で、#Div1
の左右または下の外側から*1#Div4
の内側までマウスポインタ―を動かしていったとき、以下のことが観察されます(実際にやってみてください)。
mouseover
は、マウスポインタ―が入ってきたときに発生。mouseout
は、マウスポインタ―が子要素へ移った場合にも発生。mouseenter
は、マウスポインタ―が入ってきたときに発生。mouseleave
は、マウスポインタ―が子要素へ移った場合には発生しません。
*1 - マウスポインターが各要素の上側を通過すると情報を表示している要素の境目でイベントが発生してしまうので避けるべき。
マウスポインタが要素の「内側から外側」へ移動するとき
次に、#Div4
の内側から、左右または下に向かって、#Div1
の外側までマウスポインタ―を動かすと、以下のことが観察できます(全てのstopPropagation
のチェックは外したままです)。
mouseover
は、子要素からマウスポインタ―が入ってきたときにも発生。mouseout
は、マウスポインタ―が出て行った場合に発生。mouseenter
は、子要素からマウスポインタ―が入ってきたときには発生しない。mouseleave
は、マウスポインタ―が出て行った場合に発生。
各イベントの伝播を確認
上と同じく、全てのstopPropagation
のチェックを外したままで、各イベントが発生した場合、
mouseover
とmouseout
は親要素へ伝播しますが、mouseenter
とmouseleave
は伝播しません。
そして、伝播を抑制するには、イベントハンドラーの中で event.stopPropagation()
を呼び出します。
MDNに書いてあること
MDNのmouseenter
には、以下のように書かれています。
With deep hierarchies, the amount of mouseenter events sent can be quite huge and cause significant performance problems. In such cases, it is better to listen for mouseover events.
翻訳すると「深い階層では、送信される mouseenter イベントが大量になる可能性があり、重大なパフォーマンスの問題の原因になり得る。その場合は mouseoverイベントを使うといい」とのことですが、これって上で見たことと少し雰囲気が違ってないか?と。 mouseover
が全ての親に伝播していて、たくさん発生しているように見えましたけど?
「逆じゃないの?」って、かなりMDNを疑ってみたのですが、しかし、これはこれでどうやら間違いではないみたい。
すべての要素でイベントをListenしているので、大量の mouseover
が発生していましたが、通常そんなことはしませんね。
イベントはaddEventListenerしている要素に発生して後は親要素に向かって伝播するだけ。
イベントを処理すべき要素だけにハンドラを設定しておけばなんら問題がないはず。
一方「mouseenterが大量に発生して」という部分はよくわかりません。
そのようなケースが今のところ想定できないのですが。
mouseenter
は、そもそも親要素に伝播しないし、論理的な自要素の領域内にあればmouseleave
は発生しないので、必要なところでListenしておけば問題ないような気がするし、そもそも用途が違うんじゃないのか?と。
ちょっとモヤモヤしてますが、とりあえず・・・。