dropが来ない時の確認
ドラッグ&ドロップで、dragover は来るのに drop が来ないときの確認手順です。
実際に16通りの条件でドラッグして、イベントが何回届くかを数えた結果をまとめます。
経緯は開発日記のドロップが落ちない 15分で3回直したに書きました。
測った環境は、PlaywrightのChromium 151.0.7922.34で、page.mouse による本物のマウス操作です。
1 見る順番
次の順に見ます。上の項目から順に、違っていないかを確かめます。
2 effectAllowedとdropEffectの組み合わせ
effectAllowed は、ドラッグ元が dragstart で宣言する「許す操作」です。
dropEffect は、ドロップ先が dragover で答える「どの操作で受けるか」です。
この2つが食い違うと、dragover は来続けるのに、drop だけが来ません。
測った結果は次のとおりです。dragover は、どの行も5回でした(この再現でのマウスの動かし方による回数です)。
- A:
effectAllowed = "move"、dropEffect = "copy"→ drop 0回 - B:
effectAllowed = "copyMove"、dropEffect = "copy"→ drop 1回 - C:
effectAllowed = "move"、dropEffect = "move"→ drop 1回 - D:
effectAllowedを設定しない、dropEffect = "copy"→ drop 1回 - E:
effectAllowed = "copy"、dropEffect = "move"→ drop 0回 - O:
effectAllowed = "move"、dropEffectを設定しない → drop 1回。dropのdropEffectは"move" - P: どちらも設定しない → drop 1回。
dropEffectは"copy" - N:
dragoverでpreventDefaultを呼ばない → drop 0回
迷ったら、dropEffect を設定しない(O・P)のが安全でした。
コピーのカーソルを出したいなど、dropEffect を決めたい理由があるときは、ドラッグ元の effectAllowed も合わせます(B)。
dragover が来ていても、drop が来る保証にはなりません。
dragover の中で枠を強調する作りだと、見た目が正常なまま、落とせません。
3 受ける範囲とstopPropagation
アップロード欄のように、自前とは別に、dragover と drop の処理を持つ部品の上へ落とす場合の結果です。
- H: 受ける範囲が自前の欄だけ(アップロード欄は範囲の外)
- I: 範囲を、アップロード欄を含む全体に広げた
- K: 広げて、
dropを、capture相で受けてstopPropagationした - L: 広げて、
dragoverを、capture相で受けてstopPropagationした - M: 広げて、
dragoverとdropの両方を止めた
わかったことは3つです。
- 範囲が狭いと、別の部品の既存の処理が曲を受ける(H)。受ける範囲は、
event.target.closest(...)で親の要素まで広げる - 相手の既存の
dropを動かしたくないときは、dropを、capture相で受けてstopPropagationする(K) dragoverでstopPropagationしても、dropは止まらない。ただし、相手のdragoverの処理が呼ばれなくなる(L)。自前の欄へ落とすときにdragoverを止めても、dropは来た(F・G)
4 再現の書き方
ページ側は、次の3つだけでも再現できます。
row.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("application/x-track", "曲A.wav");
// effectAllowed は設定しない
});
document.addEventListener("dragover", (e) => {
if (!e.target.closest("#zone")) return;
e.preventDefault(); // これが無いとdropは来ない(N)
}, true);
document.addEventListener("drop", (e) => {
if (!e.target.closest("#zone")) return;
e.preventDefault();
window.dropCount = (window.dropCount || 0) + 1;
}, true);
ドラッグは、Playwrightの page.mouse で、押す、動かす、離す、の順に行います。
この方法で、ブラウザの許可の判定(AとE)まで再現できました。
const src = await page.locator("#row").boundingBox();
const dst = await page.locator("#zone").boundingBox();
await page.mouse.move(src.x + 10, src.y + 10);
await page.mouse.down();
await page.mouse.move(dst.x + 20, dst.y + 10, { steps: 8 });
await page.mouse.move(dst.x + 30, dst.y + 12, { steps: 3 });
await page.mouse.up();
console.log(await page.evaluate(() => window.dropCount));
条件は、URLの ?ea=move&de=copy のように、クエリで切り替えられるようにしておくと、16通りを1つのスクリプトで回せます。
測った記録は、各条件の dragover、drop、相手の dragover、相手の drop の回数です。
5 偽のイベントのテストでは見つからない
イベントを、偽の document と偽の dataTransfer で作って、登録した関数を直接呼ぶテストは、関数の中身を確かめられます。
ただし、ブラウザが行う「許可と答えが食い違うと drop を発火させない」という判定は通りません。
同じ食い違いを、偽のイベントのテストで見つけることはできませんでした(今回の実例では、テストは通ったままでした)。
effectAllowed の値を確かめるテストを足すだけでは、drop が来ることの確認にはなりません。
「直した」の確認は、本物のドラッグで、drop が来た回数を数えて行います。
6 確かめていない範囲
- Chrome以外(Firefox、Safari)の挙動。上の結果はChromium 151だけです
- タッチ操作。マウスでのドラッグだけを測りました
- 実アプリ(Gradio)の画面での確認。代役のページで測りました
effectAllowedの、ほかの値(linkなど)の組み合わせ
関連
- 開発日記: ドロップが落ちない 15分で3回直した — 3回の直しを、実ブラウザで1つずつ再現した記録
- ガードが素通りする書き方 — 検査が効いていなかった実例と、壊して確かめる手順
- 制作物の一覧 — 作っているプロダクトの一覧