はじめに
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
と合わせて他にも感動した機能があったりしたのでいつかご紹介したいと思っています。
最後まで読んでいただきありがとうございました! 間違ってるところ等々ありましたらぜひコメントください!