HTML5 Drag and Drop APIの使用
HTML5 Drag and Drop (DnD) APIを使うということは、ページ上のほぼすべての要素をドラッグ可能にできるということです。本記事では、ドラッグ & ドロップの基礎について説明します。
ドラッグ可能なコンテンツの作成 #
ほとんどのブラウザで、テキストの選択、画像、リンクがデフォルトでドラッグ可能になっていることは注目に値するでしょう。たとえば、Google検索でGoogleロゴをドラッグすると、ゴースト画像が表示されます。次に、画像をアドレスバー、 <input type="file" />要素、またはデスクトップにドロップできます。他のタイプのコンテンツをドラッグ可能にするには、HTML5 DnD 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です。DnD 機能に関連する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;
}
この時点で、アイテムをドラッグできることがわかりますが、他には何も起こりません。 DnD機能を追加するには、JavaScript APIを使用する必要があります。
ドラッグイベントをリッスンする #
ドラッグ & ドロップのプロセス全体を監視する目的でアタッチするイベントは多岐に渡ります。
DnDフローを処理するには、ある種のソース要素(ドラッグが発生する場所)、データペイロード(ドロップしようとしているもの)、およびターゲット(ドロップをキャッチする領域)が必要です。ソース要素には、画像、リスト、リンク、ファイルオブジェクト、HTMLのブロックなどがあります。ターゲットは、ユーザーがドロップしようとしているデータを受け入れるドロップゾーン(または一連のドロップゾーン)です。すべての要素をターゲットにできるわけではないことに注意してください。たとえば、画像はターゲットにできません。
ドラッグ & ドロップシーケンスの開始と終了 #
コンテンツに対してdraggable="true"属性を定義したら、dragstartイベントハンドラーをアタッチして、各列のDnDシーケンスを開始します。
このコードは、ユーザーが列をドラッグし始めたときに列の不透明度を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を使用して視覚的な手がかりを追加する #
インターフェースの操作方法を理解しやすくするために、 3つのイベントハンドラー 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) {
if (e.preventDefault) {
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イベントでe.preventDefault()を呼び出します。同じハンドラーで、falseを返すのも良いプラクティスです。 overクラスを切り替える場合は、dragoverではなく、dragenterイベントハンドラーが使用されます。dragoverを使用すると、列のホバー時にdragoverイベントが引き続き発生するため、CSSクラスが何度も切り替えられることになります。最終的には、ブラウザのレンダラーが不要な作業を大量に行うことになります。再描画を最小限に抑えることに間違いはありません。dragoverイベントを何かに使用する必要がある場合は、イベントリスナーをスロットルまたはデバウンスすることを検討してください。
ドロップを完了する #
実際のドロップを処理するには、dropイベントのイベントリスナーを追加します。dropハンドラーでは、ドロップに対するブラウザーのデフォルト動作を防ぐ必要があります。一般的には、何らかのリダイレクトが発生します。e.stopPropagation()呼び出すと、イベントがDOMをバブリングするのを防げます。
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オブジェクトを使用する必要があります。
すべてのDnDマジックは、dataTransferプロパティで起こります。ドラッグアクションで送信されたデータを保持します。dataTransferがdragstartイベントで設定され、dropイベントで読み取り/処理されます。 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;
}
次のデモで結果を確認できます。A列をB列の上にドラッグして放し、場所がどのように変化するかを確認します。
その他のドラッグプロパティ #
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で見られることがあるかも知れません。場合によっては、たとえば、ドラッグターゲットとソースは異なる場合があります。ユーザーが製品のメイン画像として画像を1つ選択し、それをターゲットにドラッグするといったインターフェイスなどがそれに該当します。
ドラッグ & ドロップは、ユーザーがデスクトップからアプリケーションにアイテムをドラッグできるようにするためによく使用されます。主な違いは、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.
}
}
これに関する詳細は、Custom drag-and-drop (カスタムドラッグ & ドロップをご覧ください。
その他のリソース #
- The Drag and Drop Specification (ドラッグ & ドロップの仕様)
- MDN HTML Drag and Drop API
- Vanilla JavaScriptを使用してドラッグ & ドロップファイルアップローダーを作成する方法
- HTMLのDrag and Drop APIを使用した駐車ゲームの作成
- ReactでHTMLのDrag and Drop APIを使用する方法