Noh | エンジニア向け情報共有コミュニティ
Signup / Login

要素上をドラッグし続けているのにonDragLeaveが実行されます

typescript
javascript

投稿日: 2024/07/22

ファイルのドラッグアンドドロップを実装しているときに、要素上をドラッグし続けているのにonDragLeaveが実行される挙動に同ぐうした。

結論としてはドラッグ中に子要素上にカーソルが乗るとonDragLeaveが実行されていた。


GPTに聞いてみた回答

Question
要素上をドラッグし続けているのにhandleDragLeaveが実行されます

Answer

handleDragLeave が誤って実行される理由の一つは、ドラッグ対象が要素の子要素に移動すると、dragenterdragleave イベントが交互に発生するためです。これを防ぐには、relatedTarget を使用して、イベントが要素の外側で発生しているかどうかを確認する必要があります。

以下のコードは、この問題を解決するために修正されたバージョンです:

import { useState, useCallback, useEffect } from 'react'; export const Dropzone = () => { const [isDragging, setIsDragging] = useState(false); const handleDragEnter = useCallback((e) => { e.preventDefault(); e.stopPropagation(); setIsDragging(true); }, []); const handleDragLeave = useCallback((e) => { e.preventDefault(); e.stopPropagation(); // relatedTargetが親要素内に存在するかをチェック if (!e.currentTarget.contains(e.relatedTarget as Node)) { setIsDragging(false); } }, []); const handleDragOver = useCallback((e) => { e.preventDefault(); e.stopPropagation(); }, []); const handleDrop = useCallback((e) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); const files = e.dataTransfer.files; console.log(files); }, []); return ( <div style={{ border: isDragging ? "2px dashed #00f" : "2px dashed white", padding: "20px", textAlign: "center" }} onDragEnter={handleDragEnter} onDragLeave={handleDragLeave} onDragOver={handleDragOver} onDrop={handleDrop} > <input type="file" style={{ display: 'none' }} /> <p>Drag 'n' drop some files here, or click to select files</p> <em>(Only *.jpeg and *.png images will be accepted)</em> </div> ); }

この修正では、handleDragLeave が呼び出されたときに e.relatedTargete.currentTarget の外側にあるかどうかを確認しています。これにより、ドラッグ対象が要素の子要素に移動しても handleDragLeave が誤って実行されることを防げます。


ミソは

if (!e.currentTarget.contains(e.relatedTarget as Node)) { setIsDragging(false); }

の部分。

https://developer.mozilla.org/ja/docs/Web/API/MouseEvent/relatedTarget

調べているとこれと同じ実装を載せてる記事を発見。
https://qiita.com/keiliving/items/5e8b26e6567efbc15765
この記事によると2022年時点のWebKitにバグがあって動かないとのこと。
手元にMacが無いので確認できない。。。

パッといい解決方法が思いつかないが、以下のように保険をかけておくといいかもしれない。

const onDragOverInner = useCallback((e: React.DragEvent<HTMLDivElement>) => { e.preventDefault() e.stopPropagation() + if (!isDragging) setIsDragging(true) console.log("handleDragOver") }, [])
yosi

Noh を作ってるエンジニア。

目次