Edited at

伝播ルート外でイベントを補足する

自分のgistを漁っていたら伝播ルート外でイベントが発生したことを知らせるカスタムイベントを発掘しました。

利用シーンがいまいち思い浮かばないのですが、実用性はさておき、面白いなと思ったのでネタとして投稿します。


サンプル

言葉で言われても意味不明だと思うのでサンプルです。

See the Pen Out Of Event by tatesuke (@tatesuke) on CodePen.


  • AをクリックするとB,C,D,Eがオレンジ色になります


  • BをクリックするとC,D,Eがオレンジ色になります


  • CをクリックするとD,Eがオレンジ色になります

  • DをクリックするとB,C,Eがオレンジ色になります

  • EをクリックするとB,Cがオレンジ色になります

要するに、クリックイベントの伝播ルート上にないエレメントがオレンジ色になります。

このサンプルでは。以下のようにクリックイベントを捕捉出来ないエレメントに対して処理を記載しています。

// 要素外がクリックされたら背景をオレンジにする

elem.addEventListener("outofclick", function (e) {
elem.style.backgroundColor = "#FBA848";
console.log("outOfClick:" + elem.id);
});


仕組み

仕組みは単純です。

あるエレメントでイベントが発生すると、ツリー上の上位エレメントまで伝搬します。

無題2.png

上位まで伝搬したら、今度は伝播ルートを逆順にたどってマークをつけていきます。

無題3.png

マークを付け終わった後、上位ノード配下の全てのエレメントに対して次の操作を行います。


  • マークが付いていないエレメントであれば、伝搬ルート外でイベントが起きたことをカスタムイベントで知らせる

  • マークがついているエレメントであれば、マークを消す

無題.png


ソースと使い方

仕組みが単純ならソースも単純です。まずできるだけ上位のエレメントにカスタムイベントを発行させるイベントリスナを登録しておきます。

// 上位エレメントのイベントハンドラにtriggerOutOfEventを登録する

window.addEventListener("click", triggerOutOfEvent)

// 捕捉例外
function triggerOutOfEvent(originalEvent) {
var rootElem = (originalEvent.currentTarget.dataset) ? originalEvent.currentTarget : document.body;

// イベント伝播ルート上のエレメントをマーク
var currentElem = originalEvent.srcElement;
while ((currentElem !== rootElem) && (currentElem !== null)) {
currentElem.dataset.isOnRoute = "true";
currentElem = currentElem.parentElement;
}

// カスタムイベント生成
var outOfEvent = new CustomEvent("outof" + originalEvent.type, {
detail: {
srcEvent: originalEvent
}
});

// イベント伝播ルート上のエレメント以外に対してカスタムイベントをディスパッチ
[].forEach.call(rootElem.querySelectorAll("*"), function (elem) {
if (elem.dataset.isOnRoute === "true") {
elem.dataset.isOnRoute = null;
} else {
elem.dispatchEvent(outOfEvent);
}
});
}

次に、OutofEventを検知したいエレメントにイベントリスナを登録します。

elem.addEventListener("outofclick", function(e) {

elem.style.backgroundColor = "#FBA848";
console.log("outOfClick:" + elem.id);
});

これだけです。


ポリフィル

古いブラウザだとカスタムイベントが使えない可能性があります。そんなときはポリフィルです。

// CustomEventのポリフィル

(function () {

if (typeof window.CustomEvent === "function") return false;

function CustomEvent(event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}

CustomEvent.prototype = window.Event.prototype;

window.CustomEvent = CustomEvent;
})();


注意点

イベント通知をわざわざ広範囲に広げているので性能が気になるところですね。

まぁ本格的に使用することなんてないと思うけど。