JavaScript進階之實現拖拽(上)

最近作到的react項目就有一個拖拽的需求,而後大概搜索了一下,找到了star比較高的react-dnd庫,可是閱讀react-dnd的官方文檔仍是有點難受的,由於概念性比較強,因此在介紹react-dnd以前咱們來實現原生拖拽javascript

原生實現拖拽

Mouse事件實現拖拽

在h5以前,原生實現拖拽是根據Mouse事件來實現的,須要用到如下這三個事件mousedown,mouseup,mousemovecss

  • mousedown 事件在指針設備按鈕按下時觸發。
  • mouseup事件在指針設備按鈕擡起時觸發。
  • 當指針設備( 一般指鼠標 )在元素上移動時, mousemove 事件被觸發。

JavaScript三你們族

明白了上述👆三個事件方法的做用以及JavaScript三你們族,咱們來實現個簡單版的拖拽html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <img id="ball" src="https://js.cx/clipart/ball.svg" alt="">
    <script>
       const ball=document.querySelector("#ball")
       ball.onmousedown = function(event{
       let shiftX = event.clientX - ball.getBoundingClientRect().left;
       let shiftY = event.clientY - ball.getBoundingClientRect().top;
        ball.style.position = 'absolute';
        ball.style.zIndex = 1000;
        document.body.append(ball);
        moveAt(event.pageX, event.pageY);
        // 移動如今位於座標 (pageX, pageY) 上的球
        // 將初始的偏移考慮在內
        function moveAt(pageX, pageY{
        ball.style.left = pageX - shiftX + 'px';
        ball.style.top = pageY - shiftY + 'px';
        }
        function onMouseMove(event{
        moveAt(event.pageX, event.pageY);
        }
        // 在 mousemove 事件上移動球
        document.addEventListener('mousemove', onMouseMove);
        // 放下球,並移除不須要的處理程序
        ball.onmouseup = function({
        document.removeEventListener('mousemove', onMouseMove);
        ball.onmouseup = null;
        };
        };
        ball.ondragstart = function({
        return false;
    };
    
</script>
</body>
</html>
複製代碼

效果以下:html5

注意

ball.ondragstart = function({
  return false;
};
複製代碼

若是不設置這段代碼,會發生奇怪的現象,這是由於瀏覽器有本身的對圖片和一些其餘元素的拖放處理,會在咱們拖放時自動運行,這與咱們的拖放處理產生了衝突。java

HTML 拖放(Drag and Drop)

拖拽事件

HTML 的 drag & drop 使用了 DOM event model 以及從mouse events 繼承而來的 drag events 。一個典型的drag操做是這樣開始的:用戶用鼠標選中一個可拖動的(draggable)元素,移動鼠標到一個可放置的(droppable)元素,而後釋放鼠標。 在操做期間,會觸發一些事件類型,有一些事件類型可能會被屢次觸發(好比drag 和 dragover 事件類型)react

  • drag: 拖拽源
  • drop:拖拽源最終放置的目標
  • DataTransfer 對象:退拽對象用來傳遞的媒介,使用通常爲Event.dataTransfer。
  • draggable 屬性:就是標籤元素要設置draggable=true
  • ondragstart 事件:當拖拽元素開始被拖拽的時候觸發的事件,此事件做用在被拖曳元素上
  • ondragenter 事件:當拖曳元素進入目標元素的時候觸發的事件,此事件做用在目標元素上
  • ondragover 事件:拖拽元素在目標元素上移動的時候觸發的事件,此事件做用在目標元素上
  • ondrop 事件:被拖拽的元素在目標元素上同時鼠標放開觸發的事件,此事件做用在目標元素上
  • ondragend 事件:當拖拽完成後觸發的事件,此事件做用在被拖曳元素上
  • Event.preventDefault()方法:阻止默認的些事件方法等執行。在ondragover中必定要執行preventDefault(),不然ondrop事件不會被觸發。另外,若是是從其餘應用軟件或是文件中拖東西進來,尤爲是圖片的時候,默認的動做是顯示這個圖片或是相關信息,並非真的執行drop。此時須要用用document的ondragover事件把它直接幹掉。
    Event.effectAllowed 屬性:就是拖拽的效果。

注意理解上述👆的概念有注意理解react-dnd庫的apiweb

舉個例子🌰:api

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
    />

    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <style>
      body {
        font16px Arial, Helvetica, sans-serif;
      }
      li {
        width:200px;
        height40px;
        text-align: center;
        line-height40px;
        border:1px dashed #cccccc
        cursor: pointer;
        user-select: none;
        background-color: white;
        list-style: none;
      }
      .more {
        border-top1px dotted rgb(196, 196, 196);
        font-size12px;
        padding-top10px;
      }
      .more,
      .more a {
        colorrgb(96, 96, 96);
      }
    
</style>
  </head>
  <body>
    <ul>
      <li
        draggable="true"
        ondragend="dragEnd()"
        ondragover="dragOver(event)"
        ondragstart="dragStart(event)"
      >

        Apples
      </li>
      <li
        draggable="true"
        ondragend="dragEnd()"
        ondragover="dragOver(event)"
        ondragstart="dragStart(event)"
      >

        Oranges
      </li>
      <li
        draggable="true"
        ondragend="dragEnd()"
        ondragover="dragOver(event)"
        ondragstart="dragStart(event)"
      >

        Bananas
      </li>
      <li
        draggable="true"
        ondragend="dragEnd()"
        ondragover="dragOver(event)"
        ondragstart="dragStart(event)"
      >

        Strawberries
      </li>
    </ul>
    <script>
      var selected;
      const li = document.createElement("li");
      function dragOver(e{
        // 向前拖拽 向後拖拽
        // 拖動目標(drop)是否是在拖拽源(drag)的前面
        if (isBefore(selected, e.target)){         
           e.target.parentNode.insertBefore(selected, e.target);
        }else {e.target.parentNode.insertBefore(selected, e.target.nextSibling);}
      }

      function dragEnd({
        selected = null;
      }

      function dragStart(e{
        selected = e.target;
        console.log(selected)
      }

      function isBefore(el1, el2{
        var cur;
        if (el2.parentNode === el1.parentNode) {
          for (cur = el1.previousSibling; cur; cur = cur.previousSibling) {
            if (cur === el2) return true;
          }
        } else return false;
      }
    
</script>
  </body>
</html>
複製代碼

效果圖:瀏覽器

是否是很接近咱們的需求啦app

自定義拖動圖像

拖動過程當中,瀏覽器會在鼠標旁顯示一張默認圖片。固然,應用程序也能夠經過 setDragImage() 方法自定義一張圖片,以下面的例子所示。

 function dragStart(e{
        selected = e.target;
        dragstart_handler(e)
        // 延時是爲了瀏覽器能生成拖拽圖片
        setTimeout(()=>{
          selected.style.opacity='0'
        },0)
      }
   function dragstart_handler(ev{
      var img = new Image(); 
      img.src = 'https://js.cx/clipart/ball.svg'
      ev.dataTransfer.setDragImage(img, 1010);
     }
複製代碼

定義拖動效果

dropEffect 屬性用來控制拖放操做中用戶給予的反饋。它會影響到拖動過程當中瀏覽器顯示的鼠標樣式。好比,當用戶懸停在目標元素上的時候,瀏覽器鼠標也許要反映拖放操做的類型。

有 3 個效果能夠定義:

  • copy 代表被拖動的數據將從它本來的位置拷貝到目標的位置。
  • move 代表被拖動的數據將被移動。
  • link 代表在拖動源位置和目標位置之間將會建立一些關係表格或是鏈接。

參考:MDN HTML5 drag & drop 拖拽與拖放簡介

相關文章
相關標籤/搜索