日常隨筆:javascript拖拽drop和drag的使用

Drag,Drop踩坑筆記

1. 幾個概念

1. 拖拽事件

摘自MDN:

HTML drag-and-drop uses the DOM event model and drag events inherited from mouse events. A typical drag operation begins when a user selects a draggable element, drags the element to a droppable element, and then releases the dragged element.

HTML拖拽事件使用DOM事件模型,drag事件繼承於鼠標事件

一個典型的拖拽事件從用戶選擇一個draggable元素開始,將該元素託到一個droppable元素下,然後釋放該被拖拽的元素

上述的描述中有幾個點:

  • 源對象(draggable元素): 正在被拖動的對象, 該元素的draggable屬性應被設置爲true
  • 目標對象: 拖拽到的目標對象,需要對元素的drag事件進行監聽,並做相應操作才能轉化爲droppable元素

2. JS拖拽事件

事件 事件處理函數 作用對象 描述
drag ondrag 源對象 源文件被拖動觸發
dragstart ondragstart 源對象 用戶開始拖拽源對象
dragend ondragend 源對象 用戶結束拖拽操作(例如釋放鼠標按鍵和點擊ESC按鍵)
dragenter ondragenter 目標對象 拖拽源對象進入目標對象
dragover ondragover 目標對象 源對象處於目標對象上方(每幾百毫秒觸發一次)
dragleave ondragleave 目標對象 源對象離開目標對象區域
dragexit ondragexit 目標對象 元素不再爲可被選擇的目標對象
drop ondrop 目標對象 源對象落在目標對象上

1. 幾個注意點

  • 關於dragenter和dragover的注意點

摘自mdn:

A listener for the dragenter and dragover events are used to indicate valid drop targets, that is, places where dragged items may be dropped. Most areas of a web page or application are not valid places to drop data. Thus, the default handling of these events is not to allow a drop.

If you want to allow a drop, you must prevent the default handling by cancelling the event. You can do this either by returning false from an attribute-defined event listener, or by calling the event’s preventDefault() method. The latter may be more feasible in a function defined in a separate script.

根據以上描述歸納三點:

  • 通過監聽一個元素的dragenter和dragover事件可以表明一個元素是有效的目標對象
  • 如果執行上述兩個事件的默認處理函數,源對象還是不能被drop的
  • 如果想要被允許drop的話,需要通過preventDefault來該事件的默認行爲

2. 拖動案例

圖片拖動案例參考: 原生JS快速實現拖放(drag and drop)效果

  • DOM結構
<body>
    <div class="droppable">
      <div class="box" draggable="true"></div>
    </div>
    <div class="droppable"></div>
    <div class="droppable"></div>
    <div class="droppable"></div>
    <div class="droppable"></div>
    <div class="droppable"></div>
</body>
  • JS實現圖片的拖拽

主要思路:

  1. draggale元素本身在拖拽的時候,文件不會消失,因此需要手動添加一個display:none使原來的元素不顯示
  2. 對droppable的落點框進行dragenter, dragover事件的監聽,這裏要阻止默認的事件的處理函數
  3. 對drop事件的回調進行操作,當事件落下的時候進行操作
const dragElem = document.querySelector('.box');
const droppables = document.querySelectorAll('.droppable');

dragElem.addEventListener('drag', function(event) {
    console.log('-------------Drag Event--------------');
});

dragElem.addEventListener('dragstart', function() {
    console.log('-------------Drag Start Event--------------');
    setTimeout(() => {
        this.classList.add('invisible');
    }, 0);
});

dragElem.addEventListener('dragend', function() {
    console.log('-------------Drag End Event--------------');
    setTimeout(() => {
        this.classList.remove('invisible');
    }, 0);
});

droppables.forEach((elem, index) => {
    elem.addEventListener('dragenter', function(event) {
        // 如果不調用event.preventDefault,導致drop事件失效
        event.preventDefault();
        console.log(`Droppable ${index} dropover event`);
    });

    elem.addEventListener('dragover', function(event) {
        // dropover事件的默認處理函數會使得drop事件不被捕獲
        event.preventDefault();
        console.log(`Droppable ${index} dropover event`);
        this.classList.add('drag-over');
    });

    elem.addEventListener('dragleave', function(event) {
        event.preventDefault();
        console.log(`Dragleave ${index} dropover event`);
        this.classList.remove('drag-over')
    });

    elem.addEventListener('drop', function(event) {
        console.log(`Drop in ${index}`);
        setTimeout(() => {
            this.append(dragElem);
            dragElem.classList.remove('invisible');
            this.classList.remove('drag-over')
        }, 0);
    });
});

3. 幾個注意點

dataTransfer中保存的爲字符串,因此我們需要將獲取到的字符串先轉換成string,然後再轉換爲dom節點進行添加

4. 代碼實現

  • dom節點與string相互轉換的一個工具類
const HtmlStringTransfer = function() {
    this.secret = null;

    this.setSecret = function() {
        const _secret = Math.random().toString(36).substr(2);
        this.secret = _secret;
    }

    this.getString = function(HTMLNode) {
        this.setSecret();
        HTMLNode.setAttribute('id', this.secret);
        return HTMLNode.outerHTML;
    };

    this.getNode = function() {
        const _node = document.getElementById(this.secret);
        _node.removeAttribute('id');

        return _node;
    };
};
  • 利用dataTransfer.setData設置源對象
const Factory = new HtmlStringTransfer();

dragElem.addEventListener('dragstart', function(event) {
    console.log('-------------Drag Start Event--------------');

    const _sourceElement = Factory.getString(event.target);

    event.dataTransfer.setData('sourceElement', _sourceElement);
    setTimeout(() => {
        this.classList.add('invisible');
    }, 0);
});
  • 利用dataTransfer.getData在drop時獲得源對象
elem.addEventListener('drop', function(event) {
    console.log(`Drop in ${index}`);
    const sourceElement = event.dataTransfer.getData('SourceElement');
    const _sourceElement = Factory.getNode(sourceElement);

    setTimeout(() => {
        this.append(_sourceElement);
        dragElem.classList.remove('invisible');
        this.classList.remove('drag-over');
    }, 0);
});
  • 完整代碼
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Test</title>
    <style>
      body {
        background: #eee;
      }

      .droppable {
        display: inline-block;
        width: 160px;
        height: 160px;
        margin: 10px;
        border: 3px salmon solid;
        background-color: #fff;
        margin: 20px;
      }

      .box {
        width: 150px;
        height: 150px;
        background-image: url('./1.jpg');
        background-size: 150px 150px;
      }

      .drag-over {
        border-style: dashed;
      }

      .dragging {
        background-color: yellow;
      }

      .invisible {
        display: none;
      }
    </style>
  </head>
  <body>
    <div class="droppable" src-index="0">
      <div draggable="true" class="box"></div>
    </div>
    <div class="droppable" src-index="1"></div>
    <div class="droppable" src-index="2"></div>
    <div class="droppable" src-index="3"></div>
    <div class="droppable" src-index="4"></div>
    <div class="droppable" src-index="5"></div>

    <script>
      const dragElem = document.querySelector('.box');
      const droppables = document.querySelectorAll('.droppable');

      const HtmlStringTransfer = function() {
        this.secret = null;

        this.setSecret = function() {
          const _secret = Math.random().toString(36).substr(2);
          this.secret = _secret;
        }

        this.getString = function(HTMLNode) {
          this.setSecret();
          HTMLNode.setAttribute('id', this.secret);
          return HTMLNode.outerHTML;
        };

        this.getNode = function() {
          const _node = document.getElementById(this.secret);
          _node.removeAttribute('id');

          return _node;
        };
      };

      const Factory = new HtmlStringTransfer();

      dragElem.addEventListener('drag', function(event) {
        console.log('-------------Drag Event--------------');
      });

      dragElem.addEventListener('dragstart', function(event) {
        console.log('-------------Drag Start Event--------------');

        const _sourceElement = Factory.getString(event.target);

        event.dataTransfer.setData('sourceElement', _sourceElement);
        setTimeout(() => {
          this.classList.add('invisible');
        }, 0);
      });

      dragElem.addEventListener('dragend', function() {
        console.log('-------------Drag End Event--------------');
        setTimeout(() => {
          this.classList.remove('invisible');
        }, 0);
      });

      droppables.forEach((elem, index) => {
        elem.addEventListener('dragenter', function(event) {
          // 如果不調用event.preventDefault,導致drop事件失效
          event.preventDefault();
          console.log(`Droppable ${index} dropover event`);
        });

        elem.addEventListener('dragover', function(event) {
          // dropover事件的默認處理函數會使得drop事件不被捕獲
          event.preventDefault();
          console.log(`Droppable ${index} dropover event`);
          this.classList.add('drag-over');
        });

        elem.addEventListener('dragleave', function(event) {
          event.preventDefault();
          console.log(`Dragleave ${index} dropover event`);
          this.classList.remove('drag-over');
        });

        elem.addEventListener('drop', function(event) {
          console.log(`Drop in ${index}`);
          const sourceElement = event.dataTransfer.getData('SourceElement');
          const _sourceElement = Factory.getNode(sourceElement);

          setTimeout(() => {
            this.append(_sourceElement);
            dragElem.classList.remove('invisible');
            this.classList.remove('drag-over');
          }, 0);
        });
      });
    </script>
  </body>
</html>

4. 實驗結果分析

1. 實驗結果

在這裏插入圖片描述

2. 事件的監聽順序

在這裏插入圖片描述

  1. 源對象的drag start
  2. 源對象的drag事件(只要鼠標拖動就會產生該事件)
  3. 拖動後第一次產生源對象所在目標對象的dragenter事件
  4. 源對象所在的對象的dragover事件
  5. 離開原目標對象的dragleave事件
  6. 新目標對象的dragenter事件
  7. 新目標對象的dragover事件
  8. drop時新目標對象的drop事件
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章