整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          HTML5拖拽效果的實現方法

          HTML5拖拽效果的實現方法

          要為大家詳細介紹了HTML5拖放效果的實現代碼,拖放即抓取對象以后拖到另一個位置。感興趣的小伙伴們可以參考一下:

          在 HTML5 中,拖放是標準的一部分,任何元素都能夠拖放。

          Internet Explorer 9+, Firefox, Opera, Chrome, 和 Safari 支持拖動。但 Safari 5.1.2不支持拖動。

          下面看看實現效果:

          拖拽前

          拖拽后

          代碼實現:

          把draggable屬性設置為true ,才能使元素可拖動。

          然后,規定當元素被拖動時,會發生什么。

          在上面的例子中,ondragstart 屬性調用了一個函數,drag(event),它規定了被拖動的數據。

          dataTransfer.setData() 方法設置被拖數據的數據類型和值:

          在這個例子中,數據類型是 "Text",值是可拖動元素的 id ("drag1")。

          ondragover 事件規定在何處放置被拖動的數據。

          默認地,無法將數據/元素放置到其他元素中。如果需要設置允許放置,我們必須阻止對元素的默認處理方式。

          這要通過調用 ondragover 事件的 event.preventDefault() 方法:event.preventDefault()

          當放置被拖數據時,會發生 drop 事件。

          在上面的例子中,ondrop 屬性調用了一個函數,drop(event):

          調用 preventDefault() 來避免瀏覽器對數據的默認處理(drop 事件的默認行為是以鏈接形式打開)

          通過 dataTransfer.getData("Text") 方法獲得被拖的數據。該方法將返回在 setData() 方法中設置為相同類型的任何數據。

          被拖數據是被拖元素的 id ("drag1")

          把被拖元素追加到放置元素(目標元素)中

          若要實現來回拖動:

          若要在兩個地方來回拖動,只需將上面代碼稍作修改就行了.將body中的代碼改成:

          然后在style樣式中加上#div2就可以了:

          我們學習了 HTML 提供的原生拖放(drag & drop)后,是時候想一想這個東西可以用來作什么可以在什么時候使用使用的場景等等

          場景分析

          當我們在注冊成功一個賬戶時,一般網站會讓我們上傳我們的用戶頭像,或者在實名認證的時候會涉及到身份證圖片上傳到等,這時候我們可以使用input提供的file屬性進行選擇本地文件進行上傳。

          我們再想一下,當在電腦端的情況下,當用戶打開文件選擇框時再尋找圖片對應的文件夾,再進行選取文件的時候是不是會有點麻煩呢?我們可不可以讓用戶找到圖片文件,直接引入實現上傳呢?答案是可以的。

          怎么做

          經過這些分析后,我們可以嘗試使用 HTML5 提供的拖拽,使得目標元素增加讀取文件功能,然后使用 ajax 實現圖片上傳。

          談一談我們需要使用到的技術:

          • Drag & Drop: HTML5 基于拖拽的事件機制
          • File API: 可以很方便的讓 Web 應用訪問文件對象,File API 包括 FileList、Blob、File、FileReader、URI scheme,本文主要講解拖拽上傳中用到的 FileList 和 FileReader 接口。
          • FormData: FormData 是基于 XMLHttpRequest Level 2 的新接口,可以方便 web 應用模擬 Form 表單數據,最重要的是它支持文件的二進制流數據,這樣我們就能夠通過它來實現 AJAX 向后端發送文件數據了。

          HTML5 拖拽事件

          關于 Drag & Drop 拖拽事件,之前我寫過一篇專門介紹的文章,HTML5-拖拽,大家有興趣的話可以點擊鏈接查看,我在這里就不在多啰嗦了~下面直接出拖拽上傳的簡要代碼示例

          var oDragWrap=document.body;
          //拖進
          oDragWrap.addEventListener(
           "dragenter",
           function(e) {
           e.preventDefault();
           },
           false
          );
          //拖離
          oDragWrap.addEventListener(
           "dragleave",
           function(e) {
           dragleaveHandler(e);
           },
           false
          );
          //拖來拖去 , 一定要注意dragover事件一定要清除默認事件
          //不然會無法觸發后面的drop事件
          oDragWrap.addEventListener(
           "dragover",
           function(e) {
           e.preventDefault();
           },
           false
          );
          //扔
          oDragWrap.addEventListener(
           "drop",
           function(e) {
           dropHandler(e);
           },
           false
          );
          var dropHandler=function(e) {
           //將本地圖片拖拽到頁面中后要進行的處理都在這
          };
          

          獲取文件數據 HTML5 File API

          File API 中的 FileReader 接口,作為 File API 的一部分,FileReader 專門用來讀取文件。我們在這里主要介紹一些 File API 中的 FileList 接口,它主要通過兩個途徑獲取本地文件列表,一是<input type="file"/>的表單形式,另一種則是e.dataTransfer.files拖拽事件傳遞的文件信息。

          var fileList=e.dataTransfer.files;
          

          使用 files 方法將會獲取到拖拽文件的數組形式的數據,每個文件占用一個數組的索引,如果索引不存在文件數據,將返回 Null。可以通過length屬性獲取文件的數量。

          var fileNum=fileList.length;
          

          拖拽上傳需要注意的是需要判斷兩個條件

          1. 拖拽的是文件而不是頁面的元素
          2. 拖拽的是圖片而不是其他類型的文件,可以通過 file.type 屬性獲取文件的類型
          // 檢測是否是拖拽文件到頁面的操作
          if (fileList.length===0) {
           return;
          }
          // 檢測文件是不是圖片
          if (fileList[0].type.indexOf("image")===-1) {
           return;
          }
          

          下面我們看看結合之前的拖拽事件,來實現拖拽圖片并在頁面中預覽

          var dropHandler=function(e) {
           e.preventDefault(); //獲取文件列表
           var fileList=e.dataTransfer.files;
           //檢測是否是拖拽文件到頁面的操作
           if (fileList.length==0) {
           return;
           }
           //檢測文件是不是圖片
           if (fileList[0].type.indexOf("image")===-1) {
           return;
           }
           //實例化file reader對象
           var reader=new FileReader();
           var img=document.createElement("img");
           reader.onload=function(e) {
           img.src=this.result;
           oDragWrap.appendChild(img);
           };
           reader.readAsDataURL(fileList[0]);
          };
          

          當完成以上操作后,相信你可以成功的完成了拖拽圖片預覽的操作。當你查看 img 標簽時會發現,img的src屬性是一個超長的文件二進制數據,當你需要很多這種的img元素時,建議將展示區域脫離文檔流,讓其絕對定位減少頁面的 reflow

          AJAX 上傳圖片

          既然已經獲取到拖拽到web頁面中的圖片數據了,下一步就是將其發送到服務器端。

          總結

          1. 監聽拖拽: 監聽頁面元素的拖拽事件,包括: dragenter、dragover、dragleave 和drop,一定要將dragover的默認事件取消掉,不然無法觸發drop事件。如需拖拽頁面里面的元素,需要給其添加屬性draggable="true"
          2. 獲取拖拽文件: 在 drop 事件觸發后通過e.dataTransfer.files獲取拖拽文件列表,一定要將drop的默認事件取消掉,否則會默認打開文件length屬性獲取文件數量,type屬性獲取文件類型
          3. 讀取圖片數據并添加預覽圖: 實例化FileReader對象,通過其readAsDataURL(file)方法獲取文件二進制流,并監聽其onload事件,將e.result賦值給img的src屬性,最后將圖片添加到DOM中
          4. 發送圖片數據

          著互聯網的發展,人們對前端體驗的要求不斷提高,過去純點擊式的網頁操作難免讓人感到厭煩。為了使用戶操作更簡便,HTML5中新增了一項功能 - 拖拽,它允許用戶以鼠標拖拽的方式來操作網頁,這更加符合人們的操作習慣。實際上該功能更多的是依賴JavaScript API的支持。除了支持在瀏覽器內部拖拽元素外,該接口還支持從瀏覽器外部向瀏覽器內拖拽文件,它借助的是操作系統的支持以及HTML5新增的另外一個特性 - File。下面我們就一起來看HTML5的拖拽如何使用。

          拖拽的基本原理

          在HTML5中,拖拽是由一系列與拖拽相關的事件組成的,如下:

          事件名產生事件的元素事件說明dragstart被拖拽的元素拖拽開始drag被拖拽的元素拖拽過程中dragover拖拽時鼠標經過的元素被拖拽元素在當前元素上方移動dragenter拖拽時鼠標經過的元素被拖拽元素進入當前元素區域dragleave拖拽時鼠標經過的元素被拖拽元素離開當前元素區域drop拖放的目標元素有元素被拖放到了當前元素中dragend拖放的對象元素拖拽結束

          上述事件的觸發流程為:

          1. 當鼠標點擊一個元素并移動,這會在該元素上觸發dragstart和drag事件(如果元素不是鏈接或圖片,則需要設置draggable=“true”,否則不允許拖動)。
          2. 當被拖拽元素進入某個元素的區域時,會觸發該區域元素的dragenter事件。
          3. 當被拖拽元素在某個元素上方移動時,會觸發該區域元素的dragover事件。
          4. 當被拖拽元素離開某個元素時,會觸發該區域元素的dragleave事件。
          5. 釋放鼠標時,會觸發目標元素的drop事件,同時會觸發被拖拽元素的dragend事件。

          這些事件構成了一次拖拽完整的生命周期,拖拽過程中的所有行為都應該在這些事件中定義。拖拽最為核心的流程就是:在拖拽開始時,將我們需要傳遞的數據寫入一個拖拽事件對象內;當釋放鼠標時,由目標元素得到這個事件對象,并取出寫入的數據執行操作。這個過程中,鼠標所經過的元素都具備獲取該事件對象的能力。

          現在我們以一個最基本的原生拖拽為例,來講解拖拽的大致流程。該例子來自W3School(這里是截圖,如需體驗效果,請移步網頁示例原生拖拽):


          這里是兩個div,其中左邊的div里含有一張圖片,現在我們希望實現將圖片自由在兩個div內拖拽。下面是頁面的HTML結構(這里省略了css樣式代碼):

          <div id="div1">
            <img id="drag1" src="/i/eg_dragdrop_w3school.gif"/>
          </div>
          
          <div id="div2">
          
          </div>
          

          上面暫時省略了拖拽相關的事件,我們將通過一步步為元素添加事件,來講解這些事件具體的含義和用法。

          首先第一步,我們需要為圖片(img元素)設置draggable=“true”(實際上img和a元素默認就是可拖拽的,不需要設置該參數。這里為了講解原理,我們暫且把圖片當做一個普通元素看待)。于是img標簽就變成了下面的樣子:

            <img id="drag1" src="/i/eg_dragdrop_w3school.gif" draggable="true"/>
          

          現在img就成了一個可拖拽的元素。當你用鼠標在該元素上點擊并移動時,鼠標的下方就會出現一張淺色的圖片跟隨鼠標移動,這是瀏覽器的默認行為,它表示當前你正在拖拽該圖片。

          雖然瀏覽器為鼠標下方添加了一張圖片,讓用戶在視覺上認為圖片已經跟著鼠標移動了,但事實并不是這樣。如果只是添加這一個屬性,當你把圖片拖拽到右側容器并釋放鼠標后,你會發現什么都沒有發生 – 圖片仍然在原來的位置。這說明拖拽并不是只開啟元素的拖拽功能就可以。想要實現拖拽功能,最重要的是依賴一個事件對象,這個事件對象可以看做一個數據載體,而上面講到的7個拖拽事件就是允許我們在不同階段操作這個事件對象。

          我們來看這個事件對象在拖拽的過程中的具體行為。

          上面我們說到,dragstart在被拖拽元素上觸發,于是我們可以為被拖拽元素(也就是img圖片)注冊一個回調函數,它負責在拖拽開始時向事件對象寫入數據:

            <img id="drag1" src="/i/eg_dragdrop_w3school.gif" 
                draggable="true" ondragstart="drag(event)"/>
          
            <script>
              function drag(event){
                event.dataTransfer.setData("Text",event.target.id);
              }
            </script>
          

          現在img上注冊了一個dragstart回調函數。一旦我們對該圖片執行了拖拽,瀏覽器就會封裝一個拖拽對象(我們記為event),并觸發該元素的dragstart事件,并將該對象傳入我們的回調函數。得到這個對象后,我們在回調函數內只定義了一行代碼,就是將被拖拽元素的id保存在該對象的dataTransfer屬性內。由于拖拽事件是在圖片元素上觸發的,所以event.target就是這個圖片元素,此時dataTransfer內保存的就是img的id:“drag1”。setData的第一個參數“Text”表示當前存儲的數據類型是文本(或者說是普通的字符串)。如果你想看一下這個事件對象是什么樣的,它大概長這樣:

          OK,現在讓我們跳過這個令人眼花的對象。我們只需要知道這個對象里存儲了與本次拖拽相關的所有參數,而我們想要傳遞的數據也已經寫在了它的dataTransfer屬性里。

          在默認情況下,所有的DOM元素都不接受釋放行為。從視覺效果上來看,當你拖拽該圖片到某塊空白區域時,你可能會發現鼠標變成了禁用圖標(一般為一個圓圈帶一個斜線),這表示當前區域不允許釋放拖拽元素。我們可以通過事件對象的preventDefault()方法來禁止這種默認行為。比如我們現在想讓上面例子中右側的那個div允許釋放被拖拽元素,我們就可以給它注冊一個ondragover(鼠標拖拽時在當前元素上方移動會觸發該事件)回調函數,在該函數內取消瀏覽器的默認行為。代碼如下:

          <div id="div2" ondragover="allowDrop(event)">
          
          </div>
          
          <script>
            function allowDrop(event){
              event.preventDefault();
            }
          </script>
          

          取消了瀏覽器的默認行為后,當鼠標拖拽圖片移動到右側的div時,鼠標就會變成可釋放的圖標(具體圖標因瀏覽器而異),它表示當前區域接受釋放。對于上面的代碼,如果你嘗試拖拽,你會發現雖然從視覺上已經可拖拽了,但是一旦鼠標釋放,仍然什么都沒有發生,圖片并沒有按我們所想被放置到右側的容器中。

          這是為什么呢?

          因為瀏覽器沒有為我們釋放鼠標的事件定義任何默認的行為,我們想做什么事必須自己手動定義。那么瀏覽器為什么不為我們提供自動移動DOM元素的默認行為呢?原因也很簡單:為了避免歧義。我們知道,HTML是一種嵌套的結構,假如有下面兩個嵌套的div:

          當你在內部的div內釋放鼠標時,這個事件不光會被內部的div元素捕獲,還會被外部的div捕獲(包括document元素也會捕獲到這個事件),那么瀏覽器把被拖拽元素添加到哪個元素上呢?瀏覽器無法做出選擇,因此無法為開發者提供默認的行為(當然可能還有其他原因,但僅這一個原因就已經很充分了)。

          但是這個問題對開發者來說就不存在任何歧義,因為開發者可以選擇為哪個元素綁定回調來處理這個事件(當然也可以都綁定,它們都會得到執行)。回到上面的例子,現在我們希望把圖片拖放到右側div時移動圖片,于是我們給右側的div綁定一個ondrop事件來監聽鼠標的釋放事件。代碼如下:

          <div id="div2" ondrop="drop(event)" ondragover="allowDrop(event)"></div>
          
          <script>
            function drop(event){
              event.preventDefault();
              var data=event.dataTransfer.getData("Text");
              event.target.appendChild(getElementById(data));
            }
          </script>
          

          我們給右側div綁定了ondrop事件,當鼠標拖拽圖片在該區域釋放時就會執行該回調函數,瀏覽器會把拖拽開始時生成的事件對象作為參數傳遞進來(還記得嗎?我們在這個對象的dataTransfer屬性里記錄了被拖拽圖片元素的id,現在要派上用場了)。

          首先第一步,仍然是用preventDefault()禁止瀏覽器的默認行為(我們在dragover里只是保證鼠標在移動時不出現丑陋的禁用圖標,但是鼠標釋放時仍然會出現,雖然只有一瞬間,但這非常影響用戶體驗)。

          第二步,取出我們在datatTransfer里寫入的圖片元素的id。

          第三步,用原生選擇器從DOM樹中找到這個元素,使用appendChild方法添加到當前元素(此時的event.target指的是右側的容器,因為該事件是在右側容器上觸發的)上。

          所以我們真正執行的操作無非就是把圖片元素查出來,用原生的DOM方法添加到右側容器中。但是被拖拽的圖片和目標容器本身是相互獨立的,只有借助一個事件對象,目標容器才知道到底是哪個元素需要被添加進來。而這個事件的dataTransfer屬性就是數據傳遞的載體。

          經過上面的修改,這個代碼就可以實現把圖片從左側div拖拽到右側div了。為了能夠實現兩個容器的相互拖拽,我們需要為左側容器也寫上同樣的監聽事件,這樣兩個div就都具備了放置圖片元素的能力,也就是W3School示例中的效果。一個最基本的拖拽也就實現了。

          換個角度看拖拽

          (原創聲明:如需引用該部分內容,請注明出處)

          從更高的角度來說,拖拽是瀏覽器為開發者封裝的一個消息通道。這個通道的起點是被拖拽的元素,終點是目標元素。瀏覽器用一個事件對象用來描述與拖拽相關的參數,它產生于起點,被傳遞到目標元素,而鼠標經過的元素也可以從這個通道中獲取事件對象。

          用戶將鼠標放到一個元素上,按下并拖動的行為將在該元素上開啟這個消息通道。瀏覽器會生成一個用于描述該拖拽行為的事件對象,我們可以通過該對象寫入自己的數據,也可以設置與本次拖拽相關的參數(比如移動時鼠標的樣式、允許的拖拽類型等),然后該對象將在消息通道內傳遞。

          瀏覽器向我們提供了若干個原生事件來從通道中獲取這個事件對象,并執行需要執行的操作(比如當鼠標進入某個元素時,該元素可以通過ondragenter事件接口從消息通道中獲取事件對象,假如你希望鼠標從當前元素上方掠過時變為可放置的樣式,就可以通過event.preventDefault()來實現,就像我們上面做的那樣)。讓我們從新的角度重新來看瀏覽器為開發者提供的7個原生事件:

          事件名產生事件的元素角色事件說明dragstart被拖拽的元素通道起點開啟消息通道,生成事件對象,并寫入數據或設置參數等drag被拖拽的元素通道起點允許在整個拖拽過程中從消息通道里獲取事件對象dragend拖放的對象元素通道起點釋放鼠標時,瀏覽器通過該事件通知起點元素dragenter拖拽時鼠標經過的元素通道的路徑允許當鼠標進入某個元素時,從消息通道中獲取事件對象dragover拖拽時鼠標經過的元素通道的路徑允許當鼠標在某個元素上移動時,從消息通道中獲取事件對象dragleave拖拽時鼠標經過的元素通道的路徑允許當鼠標離開某個元素時,從消息通道中獲取事件對象drop拖放的目標元素通道的終點釋放鼠標時,目標元素從通道中得到事件對象

          從表格中可以看到,被拖拽的元素(通道的起點)可以在拖拽的開始和結束,以及拖拽的整個過程(通常每隔350毫秒觸發一次)中監聽該事件。鼠標經過的元素只能在鼠標從該元素上方經過時監聽到拖拽事件(這個過程又細分為進入、移動和離開)。而目標元素只能在鼠標釋放時才能監聽到該事件(因為在用戶釋放鼠標之前,我們無法知道目標元素是誰)。

          現在是不是對拖拽又有了新的認識?

          既然拖拽只是建立一個消息通道,那么我們可以傳遞的消息又何止元素的id呢?實際上該對象支持寫入四種數據類型:

          1. “text/plain”:或簡寫為"text"。純文本,也就是字符串。
          2. “text/html”:HTML格式的數據。
          3. “text/xml”:xml格式的數據。
          4. “text/url-list”:或簡寫為“url”。url列表。

          實際上第一種數據類型就可以滿足大多數情況下的需求。對于非字符串類型的數據,只需要壓縮成字符串,最后再解析為原數據結構即可(如json數據可以用JSON.stringify壓縮成字符串,再用JSON.parse解析為對象)。

          上面我們只是傳遞了被拖拽元素的id,實際上與該元素相關的任何參數,甚至與該元素無關的數據(只要我們認為它對本次拖拽有用),都可以寫入通道。而且釋放鼠標時也不一定要執行appendChild來添加元素,我們可以在釋放鼠標時做任何我們想做的事(如彈出一個提示框,或者根據傳過來的參數生成任意的DOM結構,甚至把被拖拽的元素添加到頁面的任何地方(只要你認為需要這樣做,瀏覽器都是允許的,哪怕用戶覺得很奇怪))。

          下面我將自己寫一個示例,來說明拖拽的靈活性(代碼在后面可以找到,可以直接保存為一個HTML文件雙擊運行)。

          該例子中,我們把上面的一個可拖拽按鈕的textContent(即:“可拖拽元素”這幾個字)寫入事件對象。然后為第一個div定義的拖拽行為是添加到內部的一個ul中,為第二個定義的行為是使用alert彈出提示框,為第三個定義的行為是將其顯示在第一個div內,同時在字符串前面拼上“來自第三個div的”這幾個字。

          現在當我們向第一個div內拖拽時,列表就會多出一項,文字內容為“可拖拽元素”。而向第二個div內拖拽時,就會出現如圖所示的網頁提示信息。向第三個div內拖拽時,我們看到在第一個div內列表的最后面多了一項“來自第三個div的可拖拽元素”。js代碼如下:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>HTML5原生拖拽</title>
              <style>
                  .des{
                      width:300px;
                      height:200px;
                      float: left;
                      border: 1px solid #e6e6e6;
                  }
          
                  p{
                      color: #a6a6a6;
                      font-size: 12px;
                  }
              </style>
          </head>
          <body>
          <button id="src" draggable="true" class="nav">可拖拽元素</button>
          <br/><br/>
          
          <div id="des1" class="des">
            <p>放進該區域會顯示為列表<p/>
            <ul id="container">
          
            </ul>
          </div>
          
          <div id="des2" class="des">
            <p>放進該區域會得到一條提示<p/>
          </div>
          
          <div id="des3" class="des">
            <p>放進該區域會輸出在第一個div內<p/>
          </div>
          
          </body>
          <script>
            var src=getElementById("src");
          
            var des1=getElementById("des1");
            var des2=getElementById("des2");
            var des3=getElementById("des3");
          
            src.addEventListener("dragstart", function(e){
                var dt=e.dataTransfer;
                dt.effectAllowed='all';
                dt.setData("text/plain", e.target.textContent);
            });
          
            des1.addEventListener("drop", function(e){
                var dt=e.dataTransfer;
                var text=dt.getData("text/plain");
                var container=getElementById("container");
                var li=createElement("li");
                li.textContent=text;
                container.appendChild(li);
                e.preventDefault();
                e.stopPropagation();
            }, false);
          
            des2.addEventListener("drop", function(e){
                var dt=e.dataTransfer;
                var text=dt.getData("text/plain");
                ;
                e.preventDefault();
                e.stopPropagation();
            }, false);
          
            des3.addEventListener("drop", function(e){
                var dt=e.dataTransfer;
                var text=dt.getData("text/plain");
                text="來自第三個div的" + text;
                var container=getElementById("container");
                var li=createElement("li");
                li.textContent=text;
                container.appendChild(li);
                e.preventDefault();
                e.stopPropagation();
            }, false);
          
            des1.ondragover=function(e){e.preventDefault();}
            des1.ondrop=function(e){e.preventDefault();}
            des2.ondragover=function(e){e.preventDefault();}
            des2.ondrop=function(e){e.preventDefault();}
            des3.ondragover=function(e){e.preventDefault();}
            des3.ondrop=function(e){e.preventDefault();}
          </script>
          </html>
          

          雖然是很簡單的示例,但是我想已經可以證明拖拽的靈活性。另外這里只是在釋放時有不同的行為,我們還可以對不同的元素在拖拽開始時向事件對象寫入任意的內容,這樣組合起來拖拽就會變得相當靈活。

          下面我們介紹一個將element-ui的el-tree上的節點拖拽到外部的例子,它更能說明原生拖拽的強大之處。

          el-tree中節點拖拽的擴展

          el-tree是Vue的element-ui中的樹組件,該組件提供了較為強大的拖拽功能,一個最基礎的可拖拽樹只需要寫成下面這樣即可(來自element-ui官網):

          <el-tree
            :data="data"
            node-key="id"
            default-expand-all
            @node-drag-start="handleDragStart"
            @node-drag-enter="handleDragEnter"
            @node-drag-leave="handleDragLeave"
            @node-drag-over="handleDragOver"
            @node-drag-end="handleDragEnd"
            @node-drop="handleDrop"
            draggable
            :allow-drop="allowDrop"
            :allow-drag="allowDrag">
          </el-tree>
          

          這里的事件監聽器就對應我們上面講到的原生事件,它們對每個節點都是生效的。draggable屬性表示開啟樹的拖拽功能,這樣樹的每個節點都會被添加draggable屬性。

          默認情況下,樹上的節點只支持在樹的內部拖拽。也就是說,你無法把樹上的節點拖拽到樹的外面。但是這又是一個非常常見的需求(github的issue中說el-tree具備這個能力,但是并沒有找到相關示例)。作為前端開發者,如果框架有一定的局限性,原生技術將是我們最強大的武器。下面我將簡單介紹我是如何將樹上的節點拖拽到樹的外部的,希望對感興趣的同學有所啟發。

          假設我們現在有一個容器,我們希望可以把樹上的某個節點拖拽到這個容器里形成一個列表,這個列表暫時只保留原樹節點的文本內容和id(因具體需求而異)。我們繼續使用上面的樹作為拖拽源,并給出下面一個div作為容器:

          <div class="menu-list"
              @drop="handleTargetDrop"
              @dragover="handleTargetDragOver">
              <ul>
                  <li v-for="item in menus" :key="item.id>
                      <span>{{item.name}}</span>
                  </li>
              </ul>
          </div>
          

          由于這是在Vue中,我們不需要直接操作DOM,只需要修改該ul對應的數據即可。在拖拽開始之前,menus值為空,所以該容器內不會顯示任何內容。

          現在我們先來處理el-tree。由于我們不需要改變樹結構,因此需要屏蔽樹自身的drop行為,這可以很容易通過設置綁定的allow-drop來實現,同時需要設置allow-drag使節點可拖拽:

          allowDrop(draggingNode, dropNode, type) {
              return false;
          },
          allowDrag(draggingNode) {
              return true;
          },
          

          好的,現在樹上的節點都無法在樹上移動了,并且都是可拖拽的。接下來要處理向外部拖動的行為了。我們需要定義節點的node-drag-start事件,它與原生事件的dragstart對應,是框架向我們提供的接口。我們可以在該事件的回調函數內將我們需要傳遞的數據封裝進去,為了簡單,我們直接傳遞整個節點的data即可。如下:

          handleDragStart(node, ev) {
              let dt=ev.dataTransfer;
              ev.dataTransfer.effectAllowed='copy';
              dt.setData("text/plain", JSON.stringify(node.data));
          },
          

          現在我們把樹上被拖拽的那個節點的data壓縮成json字符串寫進了事件對象的dataTransfer里。至此,樹節點已經可以向外提供節點數據了。

          下一步就是要處理我們的容器了。首先,我們要取消瀏覽器阻止拖拽的默認行為,為了用戶體驗,我們在dragover和drop中同時阻止該行為(drop的我們后面可以看到)。

          handleTargetDragOver(e){
              e.preventDefault();
          },
          

          下面就是要處理drop事件,我們需要在鼠標釋放時修改容器的列表所對應的數據menus(從這里就可以看出MVVM的設計理念,我們的視線永遠放在如何操作數據上,而不會想著如何操作DOM,因為框架會在數據變化時自動操作DOM)。實際上這相當簡單:

          handleTargetDrop(e){
              let data=e.dataTransfer;
              let content=JSON.parse(data.getData("text/plain"));
              this.menus.push({id: content.id, name: content.name});
          
              e.preventDefault();
              //通常不需要阻止冒泡,但是當出現容器嵌套時最好這么做
              //它可以防止節點被添加到數組中兩次
              e.stopPropagation();
          }
          

          我們看到,只需要非常簡單的代碼,就可以將樹上的節點拖拽到外部容器了,這再一次證明了原生拖拽的靈活性和強大。

          總結

          本文并不是一篇詳細介紹原生拖拽細節的文章。實際上拖拽事件中有很多的參數都可以設置,比如你可以設置當前拖拽只能復制,或者修改鼠標移動時跟隨鼠標移動的圖片,你還可以設置當元素進入某個區域時,底部的元素產生一定的動態效果,這樣會帶來相當高級的用戶體驗。

          此外,在事件對象的dataTransfer中還包含一個有用的屬性files,它存儲了從瀏覽器外部拖拽進來的文件,如果我們在某個元素的drop事件中讀取這個文件列表,就可以獲取用戶拖拽進來的文件,HTML5的file API允許我們直接在瀏覽器顯示該文件,或者選擇上傳到服務器等(如果你使用的是Chrome,并且征得了用戶同意,甚至可以修改這些文件,這依賴fileWriter接口,但由于安全問題,該接口的支持性不是很好)。除此之外,單憑拖拽甚至可以寫出一些有趣的HTML5網頁游戲,而這完全取決于你的創造能力。

          本文最重要的目的不是展示該技術可以被使用得多么神奇,而是希望探究它的基本原理,為以后的使用打下良好的基礎。希望對不了解原生拖拽的同學有所幫助。


          主站蜘蛛池模板: 日本在线电影一区二区三区 | 亚欧在线精品免费观看一区| 无码人妻久久一区二区三区免费| 日韩亚洲AV无码一区二区不卡 | 国产福利电影一区二区三区久久久久成人精品综合 | 国精品无码A区一区二区| 精品欧洲av无码一区二区三区| 国产拳头交一区二区| 91福利视频一区| 成人日韩熟女高清视频一区| 99精品一区二区三区无码吞精| 中文字幕无码免费久久9一区9| 国产高清在线精品一区二区| 成人h动漫精品一区二区无码| 精品一区二区久久| 视频一区精品自拍| 国产精品成人一区无码| 成人区人妻精品一区二区三区 | asmr国产一区在线| 国产一区二区三区精品久久呦| 精品无码av一区二区三区| 日韩精品一区二区三区色欲AV| 成人国内精品久久久久一区| 国产一区在线视频观看| aⅴ一区二区三区无卡无码| 毛片无码一区二区三区a片视频| 国产伦精品一区二区三区免费下载| 免费一区二区三区| 国精产品999一区二区三区有限| 无码日韩AV一区二区三区| 中文字幕一区在线| 亚洲国产精品一区二区久久hs| 91在线视频一区| 一区二区三区日本电影| 国产一区二区三区免费| 精品黑人一区二区三区| 波多野结衣一区在线观看| 熟女精品视频一区二区三区| 国模无码视频一区二区三区| 国产一区在线视频| 国产精品资源一区二区|