dragleaveを制御するカウンターを用意するためにVueのemitを使った話

こんにちはMarcoです。今作っているi4Sketchで詰まったのでメモしにきました。

個人的なメモなのでサンプルとかちゃんと用意してません*1。他の所探してもなんもわからん……って方は藁にもすがるような感じで参考にしてください。

Drag and Drop APIというのはまあ名前の通りで、Drag and DropをするためのAPIで、Drag中に貯めておくデータ置き場やらどんな処理をするかのタグ付け、あとはDragやdragenter、dragleave、dragoverなどのイベントが用意されている感じです。

詳しくはMDNとか仕様書読んでください。

本題

状況など

まず詰まったのが、子要素に移るとdragleaveが親で発火するという挙動です。

いやお前まだ中におるで……とは思いましたけど、子に入ったということは親直下から離れたのでleaveなんでしょう(多分)。

んで、stackoverflowではこれに対してcouter作るといいよ!って言われてました。発火順としてはenter -> leaveのようなので確実にカウントできるみたいです。

ただ、Vue(のようなコンポーネントベースでスコープが切られている)環境だと親コンテキストの制御に子の生イベントは使いにくいです。

生の伝播を利用すると親の親の親の……まで伝播していくので、例えばenter時になにか計算を挟んで背景色を操作する、みたいな処理をする必要があるなら制御がめんどくさすぎて吐血します*2

私のケースだと子でも処理が必要だったのと、dragoverでやるのはパフォーマンス上割けたかったのでemitさせて自前のイベントで処理することにしました。

やりたいこと

その要素内にdragがあるかを管理して、その要素からleaveするときに0であれば本当に消えたことを保証する。

子に移動したときは子のdragenterをemitして、+1(初期値) -> +2(emit enter) -> +1(dragleave)という流れを作る。戻ってくるときは子のdragleaveをemitして +1 -> +2(dragenter) -> +1(emit leave)という流れを作る。

外部に移動するときは +1 -> 0(dragleave) になるので処理をする。

発火の順序について

カウンター用途でemitするので発火順序は非常に重要になります。

div(親) > img(子)で試してみました。*3

dragleave: function (event: DragEvent) {
    console.log("leave", self.element.tagName);
    event.stopPropagation();
},
dragenter: function (event: DragEvent) {
    console.log("enter", self.element.tagName);
    self.$emit("bubbles-dragenter");
    console.log("entered", self.element.tagName);
    event.stopPropagation();
},
"bubbles-dragenter": function () {
    console.log("listen emit enter", self.element.tagName);
},
"bubbles-dragleave": function () {
    console.log("listen emit leave", self.element.tagName);
}

こんなのを用意しました。

マウスをdiv -> img -> divとしたときのconsole.logがこうなります。

  1. enter img
  2.  listen emit enter div
  3.  entered img
  4.  leave div
  5.  enter div
  6.  entered div
  7.  leave img
  8.  listen emit leave div

まずenterが発火して、そこから呼ばれたemitが発火します。

次に親にleaveが発火して一旦終了。

今度は親に動かすと、enterが発火して、子のleaveが発火、そこから呼ばれたemitが発火します。

emitされたイベントはキューに入るのではなくて即時に発火されることがわかったのでよかったですね(?)。

あ〜なんかこの記事書いたのはいいけど中身が薄くて公開するか迷うけど公開しちゃお。

自分のための記事なのでセーフ!よしっ!

*1:用意しようと思ったけど意外とめんどくさそうだったのでやめました

*2:処理が一番親だけにあるなら伝播させたほうがcounterの実装が容易だと思います

*3:手元で親の領域から投げ込みやすいのがimgだったからです。特に理由があるわけじゃないです。