React框架下Drag && Drop 實踐踩坑記錄(1)

對於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代碼視覺表現效果會不同,若是大家知道這是什麼緣由形成的,歡迎留言告訴我緣由。

相關文章
相關標籤/搜索