對於HTML5 Drag && Drop API 不熟悉的同窗,能夠先看下這篇文章,HTML5原生拖拽/拖放 Drag & Drop 詳解。本文記錄我在使用React框架在實現Drag && Drop 效果時所踩的坑。css
在開發思惟導圖組件的時候,須要實現拖拽思惟導圖中的一個節點,拖動到圖中另一個節點上時,將源節點設爲目標節點的子節點。爲了開發這一功能,採用了HTML5 Drag && Drop API。在開發過程當中有一些細節挺有意思的,在此分享出來。react
我特意寫了一個簡單的Demo來一步步演示這個過程。這個demo很簡單,僅僅是拖動一個正方形色塊到另一個正方形色塊的上面。當鼠標在目標色塊的區域內時,目標色塊的背景色變爲紅色。windows
先寫一個最簡單的例子,這樣簡單的例子只是爲了引出後面的問題。 運行效果如圖:瀏覽器
import * as React from "react";
import * as cx from "classnames";
import "./demo.scss";
interface DemoProps {}
interface DemoState {
// 是否Drag進入Dst 區域
dragEnterDst: boolean;
}
export default class Demo1 extends React.Component<DemoProps, DemoState> {
constructor(props) {
super(props);
this.state = {
dragEnterDst: false
};
}
onDragEnter = e => {
this.setState({
dragEnterDst: true
});
};
onDragLeave = e => {
this.setState({
dragEnterDst: false
});
};
render() {
return (
<div>
<div className="src" draggable></div>
<div
className={cx("dst", {
"drag-enter": this.state.dragEnterDst
})}
onDragEnter={this.onDragEnter}
onDragLeave={this.onDragLeave}
></div>
</div>
);
}
}
複製代碼
在這個簡單的例子上繼續加點料,假設目標色塊包含一個子節點。接下來會發現,當鼠標移進目標色塊區域時,觸發了目標節點的onDragEnter事件,當鼠標移進目標色塊的子節點色塊時,觸發了目標節點的onDragLeave事件。bash
代碼以下:框架
export default class Demo extends React.Component<DemoProps, DemoState> {
constructor(props) {
super(props);
this.state = {
dragEnterDst: false
};
}
onDragEnter = e => {
console.log('onDragEnter');
this.setState({
dragEnterDst: true
});
};
onDragLeave = e => {
console.log('onDragLeave')
this.setState({
dragEnterDst: false
});
};
render() {
return (
<div>
<div className="src" draggable></div>
<div
className={cx("dst", {
"drag-enter": this.state.dragEnterDst
})}
onDragEnter={this.onDragEnter}
onDragLeave={this.onDragLeave}
>
<div className='dst-sub'/>
</div>
</div>
);
}
}
複製代碼
而對於目標節點即便有子節點,我想要的效果是移動到目標節點的子節點上時,目標節點的背景色依然是紅色,只有當鼠標移出目標節點時,目標節點的紅色背景色才消失。函數
怎麼解決這個問題呢post
方法一:ui
在 dst-sub 的css 裏面設置 pointer-events: none;
這樣當鼠標移動進入dst-sub時,dst上的onDragLeave事件不會被觸發。this
方法二: 在鼠標進入dst-sub時,出發了dst上的onDragLeave事件,在事件裏面經過relatedTarget進行判斷,若是relatedTarget是dst的自身或者其子元素,那麼直接return
import * as React from "react";
import * as cx from "classnames";
import "./demo.scss";
interface DemoProps {}
interface DemoState {
// 是否Drag進入Dst 區域
dragEnterDst: boolean;
}
export default class Demo extends React.Component<DemoProps, DemoState> {
constructor(props) {
super(props);
this.state = {
dragEnterDst: false
};
}
onDragEnter = e => {
console.log('onDragEnter');
console.log(e.nativeEvent.target);
this.setState({
dragEnterDst: true
});
};
onDragLeave = e => {
console.log('onDragLeave');
let target = e.nativeEvent.target;
let relatedTarget = e.nativeEvent.relatedTarget;
// console.log(this.dst);
console.log(target);
console.log(relatedTarget);
if(this.dst==relatedTarget || this.dst.contains(relatedTarget)) {
return;
}
console.log('onDragLeave: set state false');
this.setState({
dragEnterDst: false
});
};
dst;
dstRef = (ref)=> {
this.dst = ref;
};
render() {
return (
<div>
<div className="src" draggable></div>
<div
className={cx("dst", {
"drag-enter": this.state.dragEnterDst
})}
onDragEnter={this.onDragEnter}
onDragLeave={this.onDragLeave}
ref={this.dstRef}
>
<div className='dst-sub'>
<div className='dst-sub-sub'/>
</div>
</div>
</div>
);
}
}
複製代碼
方法三:
能夠利用onDragOver事件,DragOver事件只有鼠標在目標區域裏面時就會一直觸發。用DragOver事件以後我把代碼改形成這樣
export default class Demo extends React.Component<DemoProps, DemoState> {
constructor(props) {
super(props);
this.state = {
dragEnterDst: false
};
}
onDragOver = () => {
if(!this.state.dragEnterDst) {
this.setState({
dragEnterDst: true
})
}
}
onDragLeave = (e) => {
console.log('onDragLeave');
e.preventDefault();
this.setState({
dragEnterDst: false
});
};
render() {
return (
<div>
<div className="src" draggable/>
<div
className={cx("dst", {
"drag-enter": this.state.dragEnterDst
})}
onDragOver={this.onDragOver}
onDragLeave={this.onDragLeave}
>
<div className='dst-sub'></div>
</div>
</div>
);
}
}
複製代碼
由於onDragOver事件一直被觸發,
onDragOver = () => {
if(!this.state.dragEnterDst) {
this.setState({
dragEnterDst: true
})
}
}
複製代碼
onDragOver函數寫成這樣,只有當鼠標移進dst-sub區域時,onDragLeave函數將dragEnterDst設置成false,這個時候onDragOver函數會再度將dragEnterDst設置成true。
在Mac OSX 系統的Chrome瀏覽器運行這個demo,確實達到了我想要的效果
可是在windows 10 系統的Chrome瀏覽器運行這個demo, onDragLeave函數將dragEnterDst設置成false,這個時候onDragOver函數會當即再度將dragEnterDst設置成true,我看到源色塊的背景色發生了一下抖動,由紅變橙再變紅,雖然這一過程極爲短暫,但仍是肉眼可以看到。如今我還暫時搞不明白爲何在不一樣操做系統的Chrome瀏覽器上運行一樣的demo代碼視覺表現效果會不同,若是大家知道這是什麼緣由形成的,歡迎留言告訴我緣由。