デスクトップアプリライクな操作性を実現するドラッグ&ドロップAPI

2013年3月1日(金)
山田 祥寛(YAMADA, Yoshihiro)

ドラッグ&ドロップAPIは、名前のとおり、ページ上のさまざまな要素をマウスでドラッグ&ドロップできるようにするための仕組みです。

一例を挙げると、ドラッグ&ドロップ機能とFile APIとを組み合わせることで、ブラウザに対してドロップしたファイルをそのままサーバーにアップロードできるようになるなど、よりデスクトップアプリ的な仕掛けを実装できるようになります。

[第8回目次]
  • TIPS 059:特定の要素をドラッグ可能にする
  • TIPS 060:要素をドロップ可能にする
  • TIPS 061:ドラッグ&ドロップ時のスタイルを設定する
  • TIPS 062:他のアプリからテキストデータを受け取る
  • TIPS 063:ファイルをドラッグ&ドロップする
  • TIPS 064:ドラッグ&ドロップで許可される動作を指定する
  • TIPS 065:ドラッグアイコンをカスタマイズする

仕掛けそのものは単純ですが、アプリの操作性を大きく変える可能性を持ったAPIと言えるでしょう。ドラッグ&ドロップAPIのブラウザごとの対応バージョンは、以下のとおりです。

表1:ドラッグ&ドロップAPIの対応状況

ブラウザ 対応バージョン
Internet Explorer 5.5以降(*)
Firefox 3.5以降
Chrome 4以降
Safari 5以降
Opera 12以降

*:draggable属性は10以降

サンプル一式は、会員限定特典としてダウンロードできます。記事末尾をご確認ください。

TIPS 059:特定の要素をドラッグ可能にする

ドラッグ&ドロップ機能を利用するには、まず「その要素がドラッグ可能である」ことを宣言しておく必要があります。ページ内の特定の要素をドラッグできるようにするには、対象の要素にグローバル属性であるdraggable属性を付与します。

[リスト01]
要素をドラッグ可能に(dnd.html)

  <!DOCTYPE html>
  <html>
  <head>
  <meta charset="UTF-8" />
  <title>HTML5 TIPS</title>
  ...中略...
  </head>
  <body>
  <div id="d1" draggable="true">こんにちは、赤ちゃん!</div>
  <img id="d2" src="wings.jpg" />
  </body>
  </html>
図1:要素がマウスでドラッグ可能に(クリックで拡大)

draggable属性には、true(ドラッグ可能)、false(ドラッグ不可)を指定してください。ただし、以下の要素については、デフォルトでドラッグ可能ですので、draggable属性を明示する必要はありません。

確かに、サンプルでも要素にはdraggable属性を付与していないことが確認できます。逆に、これらの要素をドラッグ不可にする場合には、明示的にdraggable属性をfalseにセットしなければなりません。

TIPS 060:要素をドロップ可能にする

要素をドラッグできるようにしただけでは、ほとんど意味がありません。一般的には、ドラッグした要素をどこかにドロップできるような仕組みを設ける必要があります。

例えば以下のサンプルは、画像をドラッグしてあらかじめ決められた領域にドロップし、移動させるための仕組みです。

[リスト02]画像をドラッグ&ドロップで移動するコード(drop.html)

  <!DOCTYPE html>
  <html>
  <head>
  <meta charset="UTF-8" />
  <title>HTML5 TIPS</title>
  <script>
  window.addEventListener('DOMContentLoaded', function() {
    // ドラッグ対象要素、ドロップ先要素を取得
    var drag = document.querySelector('#drag');
    var drop = document.querySelector('#drop');
 
    // ドラッグ開始時に、ドラッグする要素のid値をセット(1)
    drag.addEventListener('dragstart', function(e) {
      e.dataTransfer.setData('text', e.target.id);
    }, true);
 
    // dragoverイベントをキャンセル(3)
    drop.addEventListener('dragover', function(e) {
      e.preventDefault();
    }, true);
 
    // ドロップ時に、ドラッグ要素をドロップ領域に追加(移動(2))
    drop.addEventListener('drop', function(e) {
      var id = e.dataTransfer.getData('text');
      e.currentTarget.appendChild(document.getElementById(id));
      e.preventDefault();
    }, true);
  });
  </script>
  </head>
  <body>
  <!--ドラッグ可能な画像-->
  <div style="width:250px; height:70px;">
    <img id="drag" src="wings.jpg" draggable="true" />
  </div>
  <!--ドロップ先の領域-->
  <div id="drop" style="background-color:#ffc; width:450px; height:200px;"></div>
  </body>
  </html>
図2:画像を指定の領域でドロップすると、画像を移動(クリックで拡大)

ドラッグ&ドロップに関するイベントとしては、以下のようなものが用意されています。

表2:ドラッグ&ドロップ関連のイベント

分類 イベント 発生タイミング
ドラッグ dragstart ドラッグの開始
drag ドラッグ中
dragend ドラッグを終了
ドロップ dragenter ドラッグ要素がドロップ領域に入った
dragover ドラッグ要素がドロップ領域にある
dragleave ドラッグ要素がドロップ領域から出た
drop ドロップした

最低限、ドラッグ&ドロップ機能を実装するには、この中からdragstart、dragover、dropイベントリスナーを準備する必要があります。dragstartイベントでドラッグ元要素を記録し、dragoverイベントでドラッグ中の処理を、dropイベントでドロップ時の操作を、それぞれ受け持つわけです。

そして、これらのイベントリスナーで中心となるのは、DataTransferオブジェクトです。イベントオブジェクトのdataTransferプロパティ経由でアクセスできます。DataTransferオブジェクトは、ドラッグ&ドロップ時にやりとりするデータを管理するオブジェクトで、以下のようなメンバーを公開しています。

表3:DataTransferオブジェクトの主なメンバー

メンバー 概要
setData(key, value) ドラッグ&ドロップで受け渡しする情報をキー/値の組み合わせで設定
getData(key) メソッドで設定されたデータを、指定のキーで取得
clearData() メソッドで設定されたデータをクリア
setDragImage(img, x, y) ドラッグアイコンを指定(x、yでポインタからの相対位置を指定)
files 他のアプリからドラッグされたファイルを取得
effectAllowed ドラッグ要素で許可される操作
dropEffect ドロップ領域で許可される操作

まず、(1)のdragstartイベントリスナーでは、ドラッグした要素のid値を保存しておきます。この値を(2)のdropイベントリスナーで取り出し、これをキーにドラッグ元要素を操作しています(ここではドラッグ元要素をドラッグ領域の子要素として追加します)。引数keyには、テキスト情報の場合は「text」を、URL/ファイルリストの場合には「url」のいずれかを指定します。

(3)のdragoverイベントリスナーでpreventDefaultメソッドを呼び出しているのは、デフォルトの動作ではブラウザ標準の挙動が優先されてしまい、ドロップ操作が拒否されてしまうからです。そのため、ここで明示的にブラウザの挙動をキャンセルしておきます。

TIPS 061:ドラッグ&ドロップ時のスタイルを設定する

ドラッグ操作時に、操作中であることを視覚的に明確に示すため、ドラッグ元要素やドロップ領域のスタイルを変更することは、よく行われます。

以下では、dragstart/dragenter/dragleave/dropendなどのイベントを利用して、「ドラッグ時にドラッグ要素に枠線を付与し」、「ドロップ領域進入時に領域の背景色を変化」してみましょう。

[リスト03]ドラッグ元要素、ドロップ領域にスタイルを設定するコード(drop2.html)

  <!DOCTYPE html>
  <html>
  <head>
  <meta charset="UTF-8" />
  <title>HTML5 TIPS</title>
  <script>
  window.addEventListener('DOMContentLoaded', function() {
    // ドラッグ対象要素、ドロップ先要素を取得
    var drag = document.querySelector('#drag');
    var drop = document.querySelector('#drop');
 
    // ドラッグ開始
    drag.addEventListener('dragstart', function(e) {
      e.dataTransfer.setData('text', e.target.id);
      // ドラッグ元要素に対して枠線を付与
      this.style.border = 'solid 1px #f0f';
    }, true);
 
    // ドロップ領域に進入時
    drop.addEventListener('dragenter', function(e) {
      // ドロップ領域の背景を変更
      this.style.backgroundColor = '#ff0';
      e.preventDefault();
    }, true);
 
    // dragoverイベントをキャンセル
    drop.addEventListener('dragover', function(e) {
      e.preventDefault();
    }, true);
 
    // ドロップ領域から外れた時
    drop.addEventListener('dragleave', function(e) {
      // ドロップ領域の背景を元に戻す
      this.style.backgroundColor = '#ffc';
      e.preventDefault();
    }, true);
 
    // ドラッグ操作を終えた時
    drag.addEventListener('dragend', function(e) {
      // ドラッグ元要素から枠線を外す
      this.style.borderStyle = 'none';
      e.preventDefault();
    }, true);
 
    // ドロップ時
    drop.addEventListener('drop', function(e) {
      var id = e.dataTransfer.getData('text');
      e.currentTarget.appendChild(document.getElementById(id));
      e.preventDefault();
    }, true);
  });
  </script>
  </head>
  <body>
  <img id="drag" src="wings.jpg" draggable="true" />
  <div id="drop" style="background-color:#ffc; width:450px; height:200px;"></div>
  </body>
  </html>
図3:ドラッグ時にスタイルを設定(クリックで拡大)

操作自体は、ごく単純です。dragstart/dragendイベントでドラッグ元要素の枠線を、dragenter/dragleaveイベントでドロップ領域の背景色を、それぞれ操作しています。

TIPS 062:他のアプリからテキストデータを受け取る

ドラッグ&ドロップできるのは、ブラウザ(ページ)内部の要素ばかりではありません。他のアプリからドラッグしたテキストやファイルを取得し、ブラウザ内部で処理することもできます。

例えば以下は、外部のアプリで選択したテキストをブラウザ上の決められた領域にドロップする例です。ドロップされたテキストは、そのまま領域内部に表示します。

[リスト04]外部のアプリから選択テキストをドラッグ&ドロップするコード(otherDrop.html)

  <!DOCTYPE html>
  <html>
  <head>
  <meta charset="UTF-8" />
  <title>HTML5 TIPS</title>
  <script>
  window.addEventListener('DOMContentLoaded', function() {
    // ドロップ先要素を取得
    var drop = document.querySelector('#drop');
 
    // ドロップ時の処理を定義
    drop.addEventListener('drop', function(e) {
      // 他のアプリから渡されたテキストを取得
      var msg = e.dataTransfer.getData("text/plain");
      // 取得したデータをページに反映
      drop.textContent = msg;
      e.preventDefault();
    }, true);
 
    // dragoverイベントをキャンセル
    drop.addEventListener('dragover', function(e) {
      e.preventDefault();
    }, true);
  });
  </script>
  </head>
  <body>
  <div id="drop" style="background-color:#ffc; width:450px; height:200px;"></div>
  </body>
  </html>
図4:ドラッグ&ドロップしたテキストを表示(クリックで拡大)
※本サンプルは、IEでは動作しません

外部のアプリからデータを受け取るには、DataTransferオブジェクトのgetDataメソッドに、受け取るデータの種類を指定します(1)。指定できるデータの種類には、以下のようなものがあります。

表4:指定できるデータの種類(getDataメソッドの引数)

設定値 概要
text/plain 平のテキスト
text/html HTML文字列
text/xml XML文字列
text/uri-list URI、ファイル名のリスト

ここでは、「text/plain」を指定していますので、ドロップされたデータを平のテキストとして取得します。ここではtextContentプロパティで、取得したテキストをそのままページに反映させていますが、例えばカンマ区切りテキストなどを取得して、これをテーブルとして加工するようなことも可能です。

TIPS 063:ファイルをドラッグ&ドロップする

ドラッグ&ドロップ APIでは、ブラウザの外部からファイルを受け渡しすることもできます。例えば以下は、前回のTIPS55のサンプルを(ファイル入力ボックスではなく)ドラッグ&ドロップでファイルを受け渡せるように書き換えたものです。

[リスト05]ドラッグ&ドロップでファイルを指定(otherDropFile.html)

  <!DOCTYPE html>
  <html>
  <head>
  <meta charset="UTF-8" />
  <title>HTML5 TIPS</title>
  <script>
  window.addEventListener('DOMContentLoaded', function() {
    // ドロップ先要素を取得
    var drop = document.querySelector('#drop');

    // ドロップ時の処理を定義
    drop.addEventListener('drop', function(e) {
      // ドロップされたファイルを取得(1)
      var f = e.dataTransfer.files[0];
      // ファイルの読み込みに成功したら、その内容を<img id="result">に反映
      var reader = new FileReader();
      reader.addEventListener('load', function() {
        document.querySelector('#dropped').src = reader.result;
      }, false);
      // ファイルをData URL形式で取得
      reader.readAsDataURL(f);
      e.preventDefault();
    }, true);
 
    // dragoverイベントをキャンセル
    drop.addEventListener('dragover', function(e) {
      e.preventDefault();
    }, true);
  });
  </script>
  </head>
  <body>
  <div id="drop" style="background-color:#ffc; width:450px; height:200px;">
    <img id="dropped" />
  </div>
  </body>
  </html>
図5:ドラッグ&ドロップしたファイルをページに表示(クリックで拡大)
※本サンプルは、IEでは動作しません

ポイントとなるのは、実に(1)の一点のみです。ドラッグ&ドロップされたファイルは、DataTransferオブジェクトのfilesプロパティでアクセスできます。filesプロパティの戻り値はFileオブジェクトですので、あとはそのままFileReaderオブジェクトのreadAsXxxxxメソッドに引き渡すことが可能です。FileReaderオブジェクトに関する詳細は、前回の記事も合わせて参照してください。

TIPS 064:ドラッグ&ドロップで許可される動作を指定する

effectAllowed/dropEffectプロパティを指定することで、ドラッグ&ドロップ時に想定される動作を明示的に指定できます。これらのプロパティを指定しておくことで、ドラッグ&ドロップ時の動作を制限できるだけでなく、動作に応じてマウスポインタが変化し、視覚的にも現在の操作内容が明確になります。

[リスト06]ドラッグ&ドロップで許可される操作を指定(effect.html)

  <script>
  window.addEventListener('DOMContentLoaded', function() {
    // ドラッグ/ドロップ対象の要素を取得
    var drag = document.querySelector('#drag');
    var drop = document.querySelector('#drop');
 
    // ドラッグ開始時の処理を定義
    drag.addEventListener('dragstart', function(e) {
      // 現在の要素にコピーを許可
      e.dataTransfer.effectAllowed = 'copy';
      e.dataTransfer.setData('text', e.target.id);
    }, true);
 
    // ドラッグ中の処理を定義
    drop.addEventListener('dragover', function(e) {
      e.preventDefault();
      // ドロップ領域でコピーを許可
      e.dataTransfer.dropEffect = 'copy';
    }, true);
  
    // ドロップ時の処理を定義
    drop.addEventListener('drop', function(e) {
      var id = e.dataTransfer.getData('text');
      e.currentTarget.appendChild(document.getElementById(id));
      e.preventDefault();
    }, true);
  });
  </script>
図6:ドラッグ操作に応じてコピーアイコンを表示(クリックで拡大)
※本サンプルは、IEでは動作しません

まず、effectAllowedプロパティは、ドラッグ元要素で許可される動作を指定します。dragstartイベントリスナーで指定します(1)。以下に、指定できる主な値をまとめておきます。

表5:effectAllowedプロパティの設定値

設定値 概要
none すべてのドロップ操作を禁止
copy データをコピー
link データをリンク
move データを移動
copyLink データをコピー、またはリンク
copyMove データをコピー、または移動
linkMove データをリンク、または移動
all すべての操作を許可

そして、effectAllowedプロパティとセットで指定するのがdropEffectedプロパティです(2)。こちらはドロップ先の要素で許可する動作を指定します。以下は、主な設定値です。

表6:dropEeffectプロパティの設定値

設定値 概要
none すべてのドロップ操作を禁止
copy データをコピー
link データをリンク
move データを移動

dropEffectプロパティには、effectAllowedプロパティで指定した動作に対応する値を指定します。effectAllowed/dropEffectプロパティの値に応じて、マウスポインタも適した形状に変化する点に注目してください。

なお、dropEffectプロパティの値が、effectAllowedプロパティで許可した動作のいずれにも合致しない場合、マウスアイコンもドロップ禁止を表すものとなり、ドロップ操作はキャンセルされます。

図7:effectAllowed/dropEffectプロパティが一致しない場合(クリックで拡大)

TIPS 065:ドラッグアイコンをカスタマイズする

setDragImageメソッドを利用することで、ドラッグ時のアイコン画像を差し替えることもできます。アプリの動作によっては、独自のアイコンによって、より操作の結果を直感的に知らせることができるでしょう。

[リスト07]ドラッグ時のアイコンをカスタマイズするコード(icon.html)

  <script>
  window.addEventListener('DOMContentLoaded', function() {
    // ドラッグ/ドロップ対象の要素を取得
    var drag = document.querySelector('#drag');
    var drop = document.querySelector('#drop');
    
    // ドラッグ開始時の処理を定義
    drag.addEventListener('dragstart', function(e) {
      // アイコン画像を準備&ドラッグアイコンとして設定
      var icon = document.createElement('img');
      icon.src = 'icon.jpg';
      e.dataTransfer.setDragImage(icon, 0, 0);
      e.dataTransfer.setData('text', e.target.id);
    }, true);
    ...中略...
  });
  </script>
図8:ドラッグ時のアイコンを独自のicon.jpgで差替(クリックで拡大)
※本サンプルは、IEでは動作しません

setDragImageメソッドの引数には「ドラッグアイコン」「X座標」「Y座標」の順で指定します。ドラッグアイコンは、あらかじめImageオブジェクトとして用意しておきます。

X/Y座標は、アイコン画像をマウスポインタの位置からずらしたい場合に利用します。デフォルトでは、マウスポインタ位置にアイコン画像を表示します。

  • デスクトップアプリライクな操作性を実現するドラッグ&ドロップAPI

著者
山田 祥寛(YAMADA, Yoshihiro)
WINGSプロジェクト

有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表:山田祥寛)。おもな活動は、Web開発分野の書籍/雑誌/Web記事の執筆。ほかに海外記事の翻訳、講演なども幅広く手がける。2011年3月時点での登録メンバは36名で、現在もプロジェクトメンバーを募集中。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています