はじめに
React でファイルが要素の上にドラッグされたら要素を青色にしたい時は以下のように書くことが多いかと思います。
import React, {useState} from "react"
const Component = () => {
const [isHoverd, setHoverd] = useState(false);
const onDragEnter = (event: React.DragEvent<HTMLDivElement>) => {
setHoverd(true);
};
const onDragLeave = (event: React.DragEvent<HTMLDivElement>) => {
setHoverd(true);
};
return(
<div onDragEnter={onDragEnter}
onDragLeave={onDragLeave}
className={isHoverd ? "bg-blue-class" : ""}
id="drop-area"
>
ここにドロップしてね
</div>
)
}
子要素へ DragEnter すると自身の dragleave が発火しちゃう問題
上の例なら動作するんですが drop-area が子要素を持ってる場合だと問題がありまして、 drop-area 上にいても child-element に上にファイルが乗ると drop-area の dragleave が発火されちゃうんですよね...
<div id="drop-area">
<div id="child-element" /div> <- ここに Enter するとまずい
</div>
この動作の詳細な説明はこちらの記事がとても分かりやすいです。
改善その 1
この記事のやり方以外にもいい方法はないかと調べ以下の記事のやり方が僕的に好みだったので実装してみました。
やっていることはこんな感じです。(onDragLeave 以外同じなので省略してます)
const onDragLeave = (event: React.DragEvent<HTMLDivElement>) => {
if (
event.relatedTarget &&
event.currentTarget.contains(event.relatedTarget as Node)
)
return;
setHoverd(true);
};
何をやっているかというと、発生したイベントの直前に発生したイベントのエレメントを返す relatedTarget プロパティを使用して、 isHoverd を true にしたくない drop-area から child-element へのファイルの移動時はなにもしないようにしています。
WebKit だと正常に動作しない問題
上の実装で Chrome とかだと動作するんですが、safari だとなんと正常に動作しないようです。CanIuse にも言及されてないし実際に動作検証しないと気づかなそう...
onDragEnter と onDragLeave イベント時のみ relatedTarget が null になるバグ?があるみたいです。 10 年前から報告されてるとかもはや仕様レベル
どうしたか
react-area の useDrop を使用することで解消しました。解消したというのも何なら若干語弊があって useDrop のソースコードを読んでる時にこのバグの存在を知った感じです。
ライブラリを極力使用したくない場合は GitHubのこの部分に実装が書いてあるので参考になるかと思います。
さいごに
ブラウザごとに色々気をつけないといけないのはなかなか大変ですね。
また、useDrop は今回実装した onDragLeave の判定とかも元からやってくれてたりしていいですね。 onDragEnter, onDragLeave のイベント名も onDropEnter, onDropLeave にしたりしてるのが個人的には好きです。実行するのは drag してるほうじゃなくで drop されるほうですしこっちのほうがわかりやすいように思いました。
useDrop は useDrag と合わせて他にも感動した機能があったりしたのでいつかご紹介したいと思っています。
最後まで読んでいただきありがとうございました! 間違ってるところ等々ありましたらぜひコメントください!