--- title: 拖曳操作 slug: Web/API/HTML_Drag_and_Drop_API/Drag_operations translation_of: Web/API/HTML_Drag_and_Drop_API/Drag_operations --- <p>{{DefaultAPISidebar("HTML Drag and Drop API")}}</p> <p>本文會一一說明拖曳各步驟的作業。</p> <p class="note">The drag operations described in this document use the {{domxref("DataTransfer")}} interface. This document does <em>not</em> use the {{domxref("DataTransferItem")}} interface nor the {{domxref("DataTransferItemList")}} interface.</p> <h2 id="draggableattribute" name="draggableattribute">Draggable 屬性</h2> <p>網頁中有些預設的拖曳行為,例如文字選擇、圖片或超連結,當拖曳圖片或超連結時,圖片或超連結的 URL 會被當作拖曳作業中所攜帶的資料,而其他類型元素則必須另外處理才能拖曳,試試看選擇網頁某一部分,然後按住滑鼠鍵來進行拖曳,依據OS不同,或許會有一些跟著滑鼠移動的效果,但這僅僅只是預設效果行為,實際上沒有任何資料跟著被拖曳。</p> <p>In HTML, apart from the default behavior for images, links, and selections, no other elements are draggable by default. In <a href="/en-US/docs/Mozilla/Tech/XUL">XUL</a>, all elements are draggable.</p> <p>除了文字選擇、圖片或超連結之外,沒有元素預設是可拖曳的。所以要讓一個元素可以拖曳,有幾件事必須要做:</p> <ul> <li>在想要拖曳的元素上設定 <code>{{htmlattrxref("draggable")}}</code> 屬性為 <code>true</code>。</li> <li>註冊 <code>{{event("dragstart")}}</code> 事件之事件處理器。</li> <li>{{domxref("DataTransfer.setData","Set the drag data")}} within the listener defined above.</li> </ul> <p>以下是一段簡單的範例。</p> <pre class="brush: html"><div draggable="true" ondragstart="event.dataTransfer.setData('text/plain', 'This text may be dragged')"> This text <strong>may</strong> be dragged. </div> </pre> <p>draggable 為 true後,該 DIV 元素便可以拖曳,反之,倘若 draggable 為 false 或無設定則不可拖曳,只有其中下含的文字可以被選擇。draggable 屬性適用於任何元素,一般來說預設為false,除了圖片和連結預設為 true,所以說如果想要阻止圖片和連結被拖曳,則可以設定draggable 為 false。</p> <p>請注意,一旦元素被定為可拖曳之後,其下內含的文字或其他元素便無法像平常一樣用滑鼠選擇,使用者之能夠改用鍵盤或按住 Alt 鍵搭配滑鼠進行選擇。</p> <p>至於 XUL 元素則是預設皆可拖曳。</p> <pre class="brush: html"><button label="Drag Me" ondragstart="event.dataTransfer.setData('text/plain', 'Drag Me Button');"> </pre> <h2 id="dragstart" name="dragstart">開始拖曳</h2> <p>下方範例在dragstart註冊一個事件處理器。</p> <pre class="brush: html"><div draggable="true" ondragstart="event.dataTransfer.setData('text/plain', 'This text may be dragged')"> This text <strong>may</strong> be dragged. </div> </pre> <p>當拖曳作業開始,dragstart 事件會觸發,然後我們可以在事件處理器中準備好我們所要攜帶的資料、想要的拖曳回饋效果,不過基本上其實只需要準備資料就好,因為預設拖曳回饋效果已經足以應付大多數的狀況,此外,我們也可以改在上一層父元素註冊事件處理器,因為拖曳事件會上向傳遞 ( Bubble up ) 。</p> <h2 id="dragdata" name="dragdata">拖曳資料</h2> <p>所有的拖曳事件物件都有一個 <a href="/en-US/docs/DragDrop/DataTransfer" title="dataTransfer">dataTransfer</a> 屬性,這個屬性是用來攜帶資料。</p> <p>當拖曳時,資料必須和被拖曳目標作連結,比如說拖曳文字框中反白選擇的文字,那麼文字本身便是連結資料,同理,拖曳連結時URL便是連結資料。</p> <p>資料包含兩個部分,一是資料型態(或格式)、二是資料值。所謂資料型態是用文字描述資料型態(如<a href="/en-US/docs/DragDrop/Recommended_Drag_Types#text" style="line-height: 1.5;" title="text/plain">text/plain</a><span style="line-height: 1.5;">代表文字資料),而資料值則是文字,要加入拖</span>曳<span style="line-height: 1.5;">資料需要提供資料的型態和內容值;有了資料後,我們可以在dragenter或dragover事件處理器中,透過檢查資料型態來決定是否可以接受後續的放置操作,比如說只接受連結類資料的拖</span>曳<span style="line-height: 1.5;">目標區(drop target),會檢查資料型態是否為</span><a href="/en-US/docs/DragDrop/Recommended_Drag_Types#link" style="line-height: 1.5;" title="text/uri-list">text/uri-list</a><span style="line-height: 1.5;">。</span></p> <p>資料型態符合MIME型態,如<a href="/en-US/docs/DragDrop/Recommended_Drag_Types#text" style="line-height: 1.5;" title="text/plain">text/plain</a><span style="line-height: 1.5;">或</span><a href="/en-US/docs/DragDrop/Recommended_Drag_Types#image" style="line-height: 1.5;" title="image/jpeg">image/jpeg</a><span style="line-height: 1.5;">等等,而我們自己也可以自定義其他型態,最常使用的型態請見</span><a href="/en-US/docs/DragDrop/Recommended_Drag_Types" style="line-height: 1.5;" title="/en-US/docs/DragDrop/Recommended_Drag_Types">推薦拖曳資料型態</a>。</p> <p>一趟拖曳作業中可以攜帶多個多種型態的資料,所以我們可以自定義自己的型態同時,還提供其他資料給不認得自定義資料型態的其他拖曳目標區使用。通常最通用的資料會是文字類型資料。</p> <p>呼叫<a href="/en-US/docs/DragDrop/DataTransfer#setData.28.29" style="line-height: 1.5;" title="setData">setData</a><span style="line-height: 1.5;">方法,傳入資料型態和資料,這樣就可以攜帶想要的資料了:</span></p> <pre class="brush: js">event.dataTransfer.setData("text/plain", "Text to drag"); </pre> <p>上例資料是”Text to drag”文字,型態是text/plain。</p> <p><span style="line-height: 1.5;">呼叫多次setData我們就可以攜帶多種資料。</span></p> <pre class="brush: js">var dt = event.dataTransfer; dt.setData("application/x-bookmark", bookmarkString); dt.setData("text/uri-list", "http://www.mozilla.org"); dt.setData("text/plain", "http://www.mozilla.org"); </pre> <p>這裡加入了三種資料,第一種是自定義的”application/x-bookmark”,雖然有更豐富的內容可使用,但只有我們自己認識,而另外我們又為其他網站或應用加入了兩種比較常見的資料,”text/uri-list”以及”text/plain”。</p> <p>如果對同一種資料型態加入兩次資料,則新加資料會取代舊資料。</p> <p><span style="line-height: 1.5;">呼叫</span><a href="/en-US/docs/DragDrop/DataTransfer#clearData.28.29" style="line-height: 1.5;" title="clearData">clearData</a><span style="line-height: 1.5;">會清除資料。</span></p> <pre class="brush: js">event.dataTransfer.clearData("text/uri-list"); </pre> <p>如果呼叫clearData時有傳入資料型態,則只會清除該型態資料,如果沒有傳入任何型態,則所有資料皆會被清除。</p> <h2 id="dragfeedback" name="dragfeedback">設定拖曳圖片</h2> <p>當拖曳進行中,以拖曳元素為基礎,一個半透明的圖片會自動產生出來,並且跟著滑鼠移動。如果想要,我們也可以呼叫<code><a href="/en-US/docs/DragDrop/DataTransfer#setDragImage.28.29" title="setDragImage">setDragImage()來指定我們自己的拖曳使用圖片。</a></code></p> <pre class="brush: js">event.dataTransfer.setDragImage(image, xOffset, yOffset); </pre> <p>setDragImage需要三個參數,一是圖片來源(通常是圖片元素,但也可以是canvas元素或其他元素),拖曳使用圖片會依照圖片來源在螢幕上所顯示的樣子產生;二和三是圖片相對於滑鼠指標的位置位移量。</p> <p><span style="line-height: 1.5;">不過也是能夠使用文件外部的圖片或canvas元素,當需要透過canvas元素產生客製圖片時,這個技巧很有用,如下範例所示:</span></p> <pre class="brush: js">function dragWithCustomImage(event) { var canvas = document.createElementNS("http://www.w3.org/1999/xhtml","canvas"); canvas.width = canvas.height = 50; var ctx = canvas.getContext("2d"); ctx.lineWidth = 4; ctx.moveTo(0, 0); ctx.lineTo(50, 50); ctx.moveTo(0, 50); ctx.lineTo(50, 0); ctx.stroke(); var dt = event.dataTransfer; dt.setData('text/plain', 'Data to Drag'); dt.setDragImage(canvas, 25, 25); } </pre> <p>上面我們的canvas是50 x 50px大小,然後我們位移一半25讓圖片落在滑鼠指標中央。 </p> <p>在Gecko上開發,比如說外掛或Mozllia應用程式,Gecko9.0{{geckoRelease("9.0")}}支援使用{{XULElem("panel")}}元素作為拖曳圖片,簡單將XUL panel元素傳入setDragImage方法即可。</p> <p>試想下面這個 {{XULElem("panel")}}元素:</p> <pre class="brush: xml"><<span class="start-tag">panel</span><span class="attribute-name"> id</span>=<span class="attribute-value">"panel" </span><span class="attribute-name">style</span>=<span class="attribute-value">"opacity: 0.6</span><span class="attribute-value">"</span>> <<span class="start-tag">description</span><span class="attribute-name"> id</span>=<span class="attribute-value">"pb"</span>>Drag Me</<span class="end-tag">description</span>> </<span class="end-tag">panel</span>> <<span class="start-tag">vbox</span><span class="attribute-name"> align</span>=<span class="attribute-value">"start" </span><span class="attribute-name">style</span>=<span class="attribute-value">"border: 1px solid black;" </span><span class="attribute-name">ondragstart</span>=<span class="attribute-value">"startDrag(event)"</span>> <<span class="start-tag">description</span>>Drag Me</<span class="end-tag">description</span>> </<span class="end-tag">vbox</span>> </pre> <p>當使用者拖曳{{XULElem("vbox")}} 元素時,startDrag函數會被呼叫。</p> <pre class="brush: js"><span class="cdata">function startDrag(event) { event.dataTransfer.setData("text/plain", "<strong>Body</strong>"); event.dataTransfer.setDragImage(document.getElementById("panel"), 20, 20); }</span> </pre> <p>我們用HTML格式的"<strong>Body</strong>"作為資料,然後用pnael元素作為圖片。</p> <h2 id="drageffects" name="drageffects">拖曳效果</h2> <p>拖曳作業有好機種;copy作業代表被拖曳資料會被複製一份到拖曳目標區,move作業代表移動被拖曳的資料,link作業代表拖曳來源區和拖曳目標區有某種關係。</p> <p>在dragstart事件中可以設定<a href="/en-US/docs/DragDrop/DataTransfer#effectAllowed.28.29" style="line-height: 1.5;" title="effectAllowed">effectAllowed</a><span style="line-height: 1.5;">屬性,指定拖曳源頭允許的作業。</span></p> <pre class="brush: js">event.dataTransfer.effectAllowed = "copy"; </pre> <p>上面只有copy被允許,但還有其他種類:</p> <p> 只能移動或連結。</p> <dl> <dt>none</dt> <dd>不允許任何作業。</dd> <dt>copy</dt> <dd>只能複製。</dd> <dt>move</dt> <dd>只能移動。</dd> <dt>link</dt> <dd>只有連結。</dd> <dt>copyMove</dt> <dd>只能複製或移動。</dd> <dt>copyLink</dt> <dd>只能複製或連結。</dd> <dt>linkMove</dt> <dt>all</dt> <dd>複製、移動或連結皆可。</dd> </dl> <p><a href="/en-US/docs/DragDrop/DataTransfer#effectAllowed.28.29" title="effectAllowed">effectAllowed</a> 屬性預設所有作業都接受,如all值。</p> <p>在dragenter或dragover事件中,我們可以藉由檢查effectAllowed來知道那些作業是被允許的,另外,另一個相關聯的<a href="/en-US/docs/DragDrop/DataTransfer#dropEffect.28.29" style="line-height: 1.5;" title="dropEffect">dropEffect</a><span style="line-height: 1.5;">屬性應該要是effectAllowed的其中一個作業,但是dropEffect不接受多重作業,只可以是none, copy, move和link。</span></p> <p>dropEffect屬性會在在dragenter以及dragover事件中初始化為使用者想要執行的作業效果,使用者能夠透過按鍵(依平台不同,通常是Shift或Ctrl鍵),在複製、移動、連接作業之間切換,同時滑鼠指標也會跟著相應變換,例如複製作業的滑鼠旁會多出一個+的符號。</p> <p>effectAllowed和dropEffect屬性可以在dragenter或dragover事件中更改,更改effectAllowed屬性能讓拖曳作業只能在支援被允許作業類型的拖曳目標上執行,好比說effectAllowed為copyMove的作業就會阻止使用者進行link類型的作業。</p> <p>我們也可以更改dropEffect來強迫使用者執行某項作業,而且應該要是effectAllowed所列舉的作業。</p> <pre class="brush: js">event.dataTransfer.effectAllowed = "copyMove"; event.dataTransfer.dropEffect = "copy"; </pre> <p>上面的範例中copy就是會被執行的作業效果。</p> <p>若effectAllowed或dropEffect為none,那麼沒有放置作業可以被執行。</p> <h2 id="droptargets" name="droptargets">指定拖曳目標</h2> <p>dragenter和dragover事件就是用來指定拖曳目標區,也就是放置資料的目標區,絕大多數的元素預設的事件都不准放置資料。</p> <p>所以想要放置資料到元素上,就必須取消預設事件行為。取消預設事件行為能夠藉由回傳false或呼叫<a href="/en-US/docs/DOM/event.preventDefault" title="DOM/event.preventDefault">event.preventDefault</a>方法。</p> <pre class="brush: html"><div ondragover="return false"> <div ondragover="event.preventDefault()"> </pre> <p>通常我們只有在適當的時機點才需要呼叫event.preventDefault方法、取消預設事件行為,比如說被拖曳進來的是連結。所以檢查被拖曳進來的物件,當符合條件後再來取消預設事件行為。</p> <p>藉由檢查拖曳資料型態來決定是否允許放置,是最常見的作法。dataTransfer物件的<a href="/en-US/docs/DragDrop/DataTransfer#types.28.29" style="line-height: 1.5;" title="types">types</a><span style="line-height: 1.5;">屬性是一個拖曳資料型態的列表,其中順序按照資料被加入之先後排序。</span></p> <pre class="brush: js">function doDragOver(event) { var isLink = event.dataTransfer.types.contains("text/uri-list"); if (isLink) event.preventDefault(); } </pre> <p>上面我們呼叫contains方法檢察text/uri-list是否存在拖曳資料型態的列表之內,有的話才取消預設行為、准許放置作業,否則,不取消預設行為、不准許放置作業。</p> <p>檢查拖曳資料型態後,我們也可以依此更動effectAllowed和dropEffect屬性,只不過,如果沒有取消預設行為,更動並不會有甚麼影響。</p> <h3 id="Updates_to_DataTransfer.types">Updates to DataTransfer.types</h3> <p>Note that the latest spec now dictates that {{domxref("DataTransfer.types")}} should return a frozen array of {{domxref("DOMString")}}s rather than a {{domxref("DOMStringList")}} (this is supported in Firefox 52 and above).</p> <p>As a result, the <a href="/en-US/docs/Web/API/Node/contains">contains</a> method no longer works on the property; the <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes">includes</a> method should be used instead to check if a specific type of data is provided, using code like the following:</p> <pre class="brush: js">if ([...event.dataTransfer.types].includes('text/html')) { // Do something }</pre> <p>You could always use some feature detection to determine which method is supported on <code>types</code>, and run code as appropriate.</p> <h2 id="dropfeedback" name="dropfeedback">放置回饋</h2> <p>有好幾種方法回饋使用者,告訴使用者甚麼元素可以接受放置作業,最簡單的是滑鼠會指標會自動變換樣式(視平台而定)。</p> <p>滑鼠指標提示雖然夠用了,不過有時我們還是會想做其他UI上的樣式變化。-moz-drag-over的CSS pseudo-class便可以應用在拖曳目標元素上。</p> <pre class="brush: css">.droparea:-moz-drag-over { border: 1px solid black; } </pre> <p>當目標元素的dragenter預設事件有被取消時,這個pseudo-class就會啟動,目標UI會套用1px的黑色border,請注意dragover並不會檢查這項設定。</p> <p>其他比如說插入圖片等,在dragenter事件內執行更多更複雜的樣式變化也是可以的。</p> <p>倘若想要做出圖片更著滑鼠在拖曳目標區上面移動的效果,那麼可以在dragover事件內來取得的<a href="/en-US/docs/DOM/event.clientX" style="line-height: 1.5;" title="clientX">clientX</a><span style="line-height: 1.5;">和</span><a href="/en-US/docs/DOM/event.clientY" style="line-height: 1.5;" title="clientY">clientY</a><span style="line-height: 1.5;">的滑鼠座標資訊。</span></p> <p>最後,應該要在dragleave事件內復原之前所做樣式變更,dragleave事件不需要取消預設事件行為、永遠都會觸發,即使拖曳被取消了;至於使用-moz-drag-over的CSS方法的話,樣式復原會自動執行,不用擔心。</p> <h2 id="drop" name="drop">執行放置作業</h2> <p>當使用者在拖曳目標區上放開滑鼠時,drop事件就會觸發。<span style="line-height: 1.5;">當drop事件發生,我們需要取出被丟入的資料,然後處理之。</span></p> <p>要取出被丟入的資料,那就要呼叫dataTransfer物件的<a href="/en-US/docs/DragDrop/DataTransfer#getData.28.29" style="line-height: 1.5;" title="getData">getData</a><span style="line-height: 1.5;">方法。getData方法接受資料型態的參數,它會回傳</span><a href="/en-US/docs/DragDrop/DataTransfer#setData.28.29" style="line-height: 1.5;" title="setData">setData</a><span style="line-height: 1.5;">所存入的對應資料型態的資料,倘若沒有對應型態資料,那空字串就會被回傳。</span></p> <pre class="brush: js">function onDrop(event) { var data = event.dataTransfer.getData("text/plain"); event.target.textContent = data; event.preventDefault(); } </pre> <p><span style="line-height: 1.5;">上面的範例會取出文字資料,假設拖曳目標區是文字區域,例如p或div元素,那麼資料就會被當作文字內容,插入目標元素之中。</span></p> <p>網頁之中,如果我們已經處理過放置資料,那應該要呼叫{preventDefault}方法防止瀏覽器再次處理資料,比如說,Firefox預設是會開啟拖入的連結,但我們可以取消這項預設行為來避免開啟連結。</p> <p>當然也可以取得其他種類資料來使用,比如說連結資料,<a href="/en-US/docs/DragDrop/Recommended_Drag_Types#link" style="line-height: 1.5;" title="text/uri-list">text/uri-list</a><span style="line-height: 1.5;">。</span></p> <pre class="brush: js">function doDrop(event) { var links = event.dataTransfer.getData("text/uri-list").split("\n"); for each (var link in links) { if (link.indexOf("#") == 0) continue; var newlink = document.createElement("a"); newlink.href = link; newlink.textContent = link; event.target.appendChild(newlink); } event.preventDefault(); } </pre> <p>上面的範例取得連結資料,然後生成連結元素、加入頁面。從text/uri-list字面上不難猜出這種資料是一行行的URL,所以我們呼叫<a href="/en-US/docs/JavaScript/Reference/Global_Objects/String/split" style="line-height: 1.5;" title="split">split</a><span style="line-height: 1.5;">方法拆開一行行的URL,再將URL一個一個加入頁面。請注意我們有避開開頭為”#”字元的註解。</span></p> <p>更簡單的作法是採用特別URL型態。URL型態是一個特殊簡寫用途形態,它不會出現在{types}屬性中,但它可以方便的取得第一個連結,如下:</p> <pre class="brush: js">var link = event.dataTransfer.getData("URL"); </pre> <p>這個作法能夠省去檢查註解和一個一個掃過URL,但只會得到第一個URL。</p> <p>下面的例子會從多個支援的資料型態中,找出支援的資料。</p> <pre class="brush: js">function doDrop(event) { var types = event.dataTransfer.types; var supportedTypes = ["application/x-moz-file", "text/uri-list", "text/plain"]; types = supportedTypes.filter(function (value) types.contains(value)); if (types.length) var data = event.dataTransfer.getData(types[0]); event.preventDefault(); }</pre> <h2 id="dragend" name="dragend">完成拖曳</h2> <p>拖曳作業完成後,不論成功或取消於否,被拖曳元素的<code style="line-height: 1.5;"><a href="/en-US/docs/Web/Reference/Events/dragend" style="line-height: 1.5;">dragend</a></code><span style="line-height: 1.5;">事件都會觸發,如果想要判別作業是否完成,可以檢查dropEffect屬性,若是dropEffect為none,代表拖曳作業被取消,否則dropEffect的值代表所完成的作業類型。</span></p> <p>有一個Gecko專屬的<a href="/en-US/docs/DragDrop/DataTransfer#mozUserCancelled.28.29" style="line-height: 1.5;" title="dropEffect">mozUserCancelled</a><span style="line-height: 1.5;">屬性,當使用者按ESC鍵取消拖曳後,這個屬性會為true,但若是因其他理由被取消或成功,則為false</span></p> <p>拖曳作業的放置可以發生在同一個視窗或其他應用程式,而且dragend事件還是會觸發,不過事件中的<a href="/en-US/docs/DOM/event.screenX" style="line-height: 1.5;" title="screenX">screenX</a><span style="line-height: 1.5;">與</span><a href="/en-US/docs/DOM/event.screenY" style="line-height: 1.5;" title="screenY">screenY</a><span style="line-height: 1.5;">屬性會是放置發生點的資訊。</span></p> <p>當dragend事件結束傳遞後,拖曳作業也完成了。</p> <p>[1] 在Gecko,如果被拖曳元素在拖曳作業還在進行中移動或刪除,那麼dragend事件就不會觸發。<a class="external" href="https://bugzilla.mozilla.org/show_bug.cgi?id=460801" title="New D&D API: dragend is not dispatched if the source node was moved or removed during the drag session">bug 460801</a></p> <div class="s3gt_translate_tooltip" id="s3gt_translate_tooltip" style="position: absolute; left: 107px; top: 546px; opacity: 0;"> <div class="s3gt_translate_tooltip_mini" id="s3gt_translate_tooltip_mini_logo" title="翻譯選取的文字"> </div> <div class="s3gt_translate_tooltip_mini" id="s3gt_translate_tooltip_mini_sound" title="朗讀"> </div> <div class="s3gt_translate_tooltip_mini" id="s3gt_translate_tooltip_mini_copy" title="將文字複製到剪貼簿"> </div> </div> <h2 id="See_also" name="See_also">參見</h2> <ul> <li><a class="internal" href="/Web/API/HTML_Drag_and_Drop_API" title="HTML Drag and Drop API">HTML Drag and Drop API (Overview)</a></li> <li><a class="internal" href="/Web/Guide/HTML/Dragging_and_Dropping_Multiple_Items" title="Dragging and Dropping Multiple Items">Dragging and Dropping Multiple Items</a></li> <li><a class="internal" href="/Web/Guide/HTML/Recommended_Drag_Types" title="Recommended Drag Types">Recommended Drag Types</a></li> <li><a href="https://html.spec.whatwg.org/multipage/interaction.html#dnd" title="Drag and Drop Standard">HTML5 Living Standard: Drag and Drop</a></li> </ul>