這篇文章說明拖曳功能的基本概念。
建立可拖曳的內容
在大多數瀏覽器中,系統預設可拖曳文字選取項目、圖片和連結。舉例來說,如果您在網頁上拖曳連結,就會看到一個小方塊,其中包含標題和網址,您可以將其放到網址列或電腦桌面上,建立捷徑或前往連結。如要將其他類型的內容設為可拖曳,您必須使用 HTML5 拖曳 API。
如要將物件設為可拖曳,請在該元素上設定 draggable=true
。幾乎所有項目都能拖曳,包括圖片、檔案、連結、檔案或頁面上的任何標記。
以下範例會建立介面,用於重新排列已使用 CSS 格線排版的資料欄。資料欄的基本標記如下所示,其中每個資料欄的 draggable
屬性已設為 true
:
<div class="container">
<div draggable="true" class="box">A</div>
<div draggable="true" class="box">B</div>
<div draggable="true" class="box">C</div>
</div>
以下是容器和方塊元素的 CSS。與拖曳功能相關的 CSS 只有 cursor: move
屬性。程式碼的其餘部分會控制容器和方塊元素的版面配置和樣式。
.container {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
}
.box {
border: 3px solid #666;
background-color: #ddd;
border-radius: .5em;
padding: 10px;
cursor: move;
}
您可以拖曳項目,但不會執行其他動作。如要新增行為,您必須使用 JavaScript API。
監聽拖曳事件
如要監控拖曳程序,您可以監聽下列任一事件:
如要處理拖曳流程,您需要某種來源元素 (拖曳開始的位置)、資料酬載 (拖曳的項目) 和目標 (接收拖曳的區域)。來源元素幾乎可以是任何類型的元素。目標是接受使用者要捨棄的資料的捨棄區域或一組捨棄區域。並非所有元素都能做為目標目標。舉例來說,目標不得是圖片。
開始及結束拖曳序列
在內容上定義 draggable="true"
屬性後,請附加 dragstart
事件處理常式,以便啟動每個資料欄的拖曳序列。
這段程式碼會在使用者開始拖曳時將資料欄的透明度設為 40%,然後在拖曳事件結束時將其設為 100%。
function handleDragStart(e) {
this.style.opacity = '0.4';
}
function handleDragEnd(e) {
this.style.opacity = '1';
}
let items = document.querySelectorAll('.container .box');
items.forEach(function (item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragend', handleDragEnd);
});
您可以前往下方的 Glitch 示範頁面查看結果。拖曳項目並可變更透明度。由於來源元素具有 dragstart
事件,因此將 this.style.opacity
設為 40% 時,可為使用者提供視覺回饋,指出該元素是移動中的目前選取項目。捨棄項目時,即使尚未定義放置行為,來源元素會回傳至 100% 的不透明度。
新增其他視覺提示
為協助使用者瞭解如何與您的介面互動,請使用 dragenter
、dragover
和 dragleave
事件處理常式。在這個範例中,除了可拖曳之外,資料欄也是放置目標。當使用者將拖曳的項目移到資料欄上時,以虛線顯示邊框,協助使用者瞭解這點。舉例來說,您可以在 CSS 中為元素建立 over
類別,以便做為放置目標:
.box.over {
border: 3px dotted #666;
}
然後在 JavaScript 中設定事件處理常式,在資料欄拖曳時新增 over
類別,並在拖曳的元素離開時移除類別。在 dragend
處理常式中,我們也會務必移除拖曳結束時的類別。
document.addEventListener('DOMContentLoaded', (event) => {
function handleDragStart(e) {
this.style.opacity = '0.4';
}
function handleDragEnd(e) {
this.style.opacity = '1';
items.forEach(function (item) {
item.classList.remove('over');
});
}
function handleDragOver(e) {
e.preventDefault();
return false;
}
function handleDragEnter(e) {
this.classList.add('over');
}
function handleDragLeave(e) {
this.classList.remove('over');
}
let items = document.querySelectorAll('.container .box');
items.forEach(function(item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('dragenter', handleDragEnter);
item.addEventListener('dragleave', handleDragLeave);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('drop', handleDrop);
});
});
這個程式碼中有一些值得注意的重點:
dragover
事件的預設動作是將dataTransfer.dropEffect
屬性設為"none"
。我們會在本頁後續部分說明dropEffect
屬性。現階段,只要知道這會導致無法觸發drop
事件。如要覆寫這項行為,請呼叫e.preventDefault()
。另一個最佳做法是在相同的處理常式中傳回false
。dragenter
事件處理常式可用來切換over
類別,而非dragover
。如果使用dragover
,使用者將拖曳的項目按住資料欄時,事件會重複觸發,導致 CSS 類別切換重複。這會導致瀏覽器執行許多不必要的轉譯作業,進而影響使用者體驗。強烈建議您盡量減少重新繪製次數,如果您需要使用dragover
,請考慮節流或取消彈跳事件監聽器。
開賽
如要處理放置動作,請為 drop
事件新增事件監聽器。在 drop
處理常式中,您需要防止瀏覽器的預設當機行為,這通常為造成困擾的重新導向行為。如要執行此操作,請呼叫 e.stopPropagation()
。
function handleDrop(e) {
e.stopPropagation(); // stops the browser from redirecting.
return false;
}
請務必註冊新的處理常式與其他處理常式:
let items = document.querySelectorAll('.container .box');
items.forEach(function(item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('dragenter', handleDragEnter);
item.addEventListener('dragleave', handleDragLeave);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('drop', handleDrop);
});
如果您在此時執行程式碼,項目就不會放到新位置。如要這麼做,請使用 DataTransfer
物件。
dataTransfer
屬性會保留透過拖曳動作傳送的資料。dataTransfer
會在 dragstart
事件中設定,並在放置事件中讀取或處理。呼叫 e.dataTransfer.setData(mimeType, dataPayload)
即可設定物件的 MIME 類型和資料酬載。
在這個範例中,我們會讓使用者重新排列資料欄的順序。為此,您必須先在拖曳開始時儲存來源元素的 HTML:
function handleDragStart(e) {
this.style.opacity = '0.4';
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
在 drop
事件中,您要處理資料欄捨棄作業,方法是將來源資料欄的 HTML 設為您已放置資料的目標資料欄 HTML。包括確認使用者不會放回原本所在的資料欄。
function handleDrop(e) {
e.stopPropagation();
if (dragSrcEl !== this) {
dragSrcEl.innerHTML = this.innerHTML;
this.innerHTML = e.dataTransfer.getData('text/html');
}
return false;
}
您可以在下列示範中查看結果。如要使用這項功能,您必須使用電腦版瀏覽器。行動裝置不支援拖曳 API。拖曳並放開 B 欄頂端的 A 欄,您會發現這些資料欄的變化位置:
更多拖曳屬性
dataTransfer
物件會公開屬性,在拖曳過程中為使用者提供視覺回饋,並控制每個放置目標對特定資料類型的回應方式。
dataTransfer.effectAllowed
會限制使用者可在元素上執行的「拖曳類型」。用於拖曳處理模型,可在dragenter
和dragover
事件期間初始化dropEffect
。這個屬性可使用以下值:none
、copy
、copyLink
、copyMove
、link
、linkMove
、move
、all
和uninitialized
。dataTransfer.dropEffect
會控制使用者在dragenter
和dragover
事件期間取得的意見回饋。當使用者將遊標懸停在目標元素上時,瀏覽器的遊標會指出正在進行的作業類型,例如複製或移動。效果可採用下列其中一個值:none
、copy
、link
、move
。e.dataTransfer.setDragImage(imgElement, x, y)
代表可以設定拖曳圖示,而非使用瀏覽器的預設「幽靈圖片」意見回饋。
上傳檔案
這個簡單的範例會同時使用資料欄做為拖曳來源和拖曳目標。這可能會在 UI 要求使用者重新排列項目時顯示。在某些情況下,拖曳目標和來源可能為不同的元素類型,例如在使用者需要拖曳所選圖片至目標,以便選取一張圖片做為產品主要圖片的介面中。
拖曳功能經常用於讓使用者將電腦桌面上的項目拖曳至應用程式。主要差異在於 drop
處理常式。與其使用 dataTransfer.getData()
存取檔案,其資料會納入 dataTransfer.files
屬性中:
function handleDrop(e) {
e.stopPropagation(); // Stops some browsers from redirecting.
e.preventDefault();
var files = e.dataTransfer.files;
for (var i = 0, f; (f = files[i]); i++) {
// Read the File objects in this FileList.
}
}
詳情請參閱「自訂拖曳」。