要素上をドラッグし続けているのにonDragLeaveが実行されます
typescript
javascript
投稿日: 2024/07/22
ファイルのドラッグアンドドロップを実装しているときに、要素上をドラッグし続けているのにonDragLeaveが実行される挙動に同ぐうした。
結論としてはドラッグ中に子要素上にカーソルが乗るとonDragLeaveが実行されていた。
GPTに聞いてみた回答
Question
要素上をドラッグし続けているのにhandleDragLeaveが実行されます
Answer
handleDragLeave
が誤って実行される理由の一つは、ドラッグ対象が要素の子要素に移動すると、dragenter
と dragleave
イベントが交互に発生するためです。これを防ぐには、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.relatedTarget
が e.currentTarget
の外側にあるかどうかを確認しています。これにより、ドラッグ対象が要素の子要素に移動しても handleDragLeave
が誤って実行されることを防げます。
ミソは
if (!e.currentTarget.contains(e.relatedTarget as Node)) { setIsDragging(false); }
の部分。
調べているとこれと同じ実装を載せてる記事を発見。
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") }, [])