サイト内検索

ネイティブのドラッグ & ドロップ

この記事は、html5doctor に掲載されている記事「Native Drag and Drop」を日本語訳したものです。

一部、直訳ではなく意訳した部分がございます。原文と表現が異なることがございますので、ご了承ください。この本日本語訳には、翻訳上の誤りがある可能性があります。したがって、内容について一切保証をするものではありません。正確さを求める場合には、必ず各記事の原文を参照してください。当方は、この文書によって利用者が被るいかなる損害の責任を負いません。もし誤りなどを見つけたら、当サイトのお問い合わせより連絡いただければ幸いです。

ネイティブのドラッグ & ドロップ

数多くの JavaScript API とともに、HTML5 にはドラッグ・アンド・ドロップ (DnD) API が加えられた。これは、ブラウザーがネイティブの DnD をサポートし、より簡単にコーディングできるようになるのだ。

HTML5 の DnD はマイクロソフトのオリジナルの実装に基づいている。これは、Internet Explorer 5 から利用可能だったのだ!今では IE、Firefox 3.5、Safari 4 でサポートされている。

DnD の現在の実装を使って、それををどうやって組み込むのかを見ていき、そして、いくつかの問題(乗り越えなければならない束縛)を洗い直してみよう。

私の初めてのドラッグ・アンド・ドロップ

DnD に必要なことは、たったこれだけだ:

  • ドラッグするもの
  • ドラッグのターゲット
  • ブラウザーにドロップできることを伝えるためターゲット上に用意する JavaScript イベント・ハンドラ

次の要素はデフォルトでドラッグ可能だ:<img> 要素と <a> 要素(hrefがあること)

もし違う要素をドラッグしたいなら、draggable 属性に true をセットしておく必要がある。仕様にはそうかかれているが、Safari と Firefox で動作するようにするには、もう少しやらなければいけないことがある。それについて説明しよう。

コードのひとつずつの説明を見るのが面倒なら、このシンプルなドラッグ・アンド・ドロップ (ソース・コード) をご覧あれ。

このマークアップ例を見てほしい:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset=utf-8 />
<title>Basic Drag and Drop</title>
<style>
#drop {
  min-height: 100px;
  width: 200px;
  border: 3px dashed #ccc;
  margin: 10px;
  padding: 10px;
}
p {
  margin: 3px 0;
}
</style>
<script src="http://html5demos.com/h5utils.js"></script>
</head>
<body>
  <img src="http://twivatar.org/rem" alt="Remy Sharp" />
  <img src="http://twivatar.org/brucel" alt="Bruce Lawson" />
  <img src="http://twivatar.org/Rich_Clark" alt="Rich Clark" />
  <div id="drop"></div>
</body>
</html>

注意:h5utils.js というスクリプトが組み込まれているが、これは HTML5 要素を対象に、クロス・ブラウザーでイベントをバインディングできるようにする、ちょっとしたライブラリだ。

現在のところ、<img> はドラッグ可能だが、ドロップはできない(これはブラウザーのデフォルトだ)。

要素をドロップできるようにするには、次のことが必要だ:

  1. その要素は特定の地点でドラッグできることをブラウザーに伝える
  2. drop イベントをハンドリングする

この要素の中にドロップできることをブラウザーに伝えるためには、dragover イベントをキャンセルするだけでいい。しかし、IE は違った動きをするので、dragenter イベントにも同じことをしてやる必要がある。

これは、基本的な drop ハンドリングのコードだ:

var drop = document.querySelector('#drop');

// Tells the browser that we *can* drop on this target
addEvent(drop, 'dragover', cancel);
addEvent(drop, 'dragenter', cancel);

addEvent(drop, 'drop', function (event) {
  // stops the browser from redirecting off to the text.
  if (event.preventDefault) {
    event.preventDefault();
  }

  this.innerHTML += '<p>' + event.dataTransfer.getData('Text') + '</p>';

  return false;
});

function cancel(event) {
  if (event.preventDefault) {
    event.preventDefault();
  }
  return false;
}

このコードで注意してほしいのは、通常は element.addEventListener を使うところで addEvent を使っている点だ。これは h5utils.js ライブラリからのもので、IE をサポートする。

この JavaScript の中では次のことをしている:

  1. document.querySelector (これは最初の結果だけを返す) を使って、DOM からドロップ先のターゲットを探す
  2. dragover イベントが発出されたら(ユーザーがその要素を別のところにドラッグしたら)、 ‘cancel’ と呼ばれる関数が呼び出される(前述のコードの最後)。これはブラウザーのデフォルト・アクションを抑止する。
  3. IE にも対応するために、dragenter でも同じことをする。
  4. drop イベントをバインドし、その中で、何がドロップされたのかに関するデータを取り出す。

dragover と dragenter

このイベントをキャンセルすることで、私たちの下にある要素の上で離してドロップできるということを、ブラウザーに伝えることになる。

未だに私にはなぜ違いがあるのか全く分からないが、Firefox とその派生ブラウザーは、そのイベントで preventDefault が必要だが、IE は return false としなければいけない。ここ以外で他に出回っている例では、インラインの JavaScript を使っている点に注目してほしい(ゲッ!最悪だ)。preventDefault は暗黙的であるはずだから、そのコードの中にそれはないだろう。

drop

何がドロップされたかを見せるために、2つのことをする必要がある:

  1. ブラウザーのデフォルト・アクションをキャンセルする。これによって、もしリンクをドロップしても、ブラウザーがそのローケーションにサーフしてしまわないようにする。
  2. そのコンテンツを dataTransfer オブジェクトから取得する

preventDefaultreturn false を使って、もう一度、ブラウザーのデフォルトをキャンセルするのだ。

もし手動で dataTransfer データをセットしないと(後で見せるつもりだ)、デフォルトのキーは、Text にセットされる。だから、これは、<a> 要素の href の値、または、<img> の src のアドレスになるだろう。

ドラッグ & ドロップでもっとできること

仕様には、イメージをドラッグするだけ以外にもたくさんのことが書いてある。

私が使えると思ったものをいくつか挙げよう:

  1. どんな要素でもドラッグ可能にできる。
  2. テキスト以外にも、もっと複雑なデータ・タイプを扱える。
  3. 要素をドラッグするときに、違うアイコンやイメージをセットできる。

何でもドラッグできるようにする

もし <img> を、背景画像を伴った <div> に変えても、私はページ上でドラッグして、私のコンテナの中に入れることができるようにしたい。

HTML5 仕様では、それは該当の要素のマークアップに次の属性を加えるだけで良いと言っている:

draggable="true"

しかし、これは Safari や Firefox では全く効かない。

Safari では、該当の要素に次のスタイルを加える必要がある:

[draggable=true] {
  -khtml-user-drag: element;
}

これは Safari で動作するだろう。ドラッグしたら、それはデフォルト、つまり dataTransfer オブジェクトを伴った空の値がセットされるだろう。しかし、Firefox では、あなたが手動でそうなるようにいくつかのデータをセットしない限り、その要素をドラッグすることはできないだろう。

これを解決するために、dragstart イベント・ハンドラが必要となる。ドラッグできるようにするために、何らかのデータをそれに与えるのだ:

var dragItems = document.querySelectorAll('[draggable=true]');

for (var i = 0; i < dragItems.length; i++) {
  addEvent(dragItems[i], 'dragstart', function (event) {
    // store the ID of the element, and collect it on the drop later on
    event.dataTransfer.setData('Text', this.id);
  });
}

この何でもドラッグ・アンド・ドロップできる動作デモを見てほしい。(ソース・コード)

もっと複雑なデータ・タイプ

これまでの例で、何らかのデータを関連付けるために dataTransfer.setData(format, string) を使うことができることを見てきた。

あなたは、もっと多くのデータを蓄積するために key/value ペアを使うこともできるのだ。だが、扱えるのは文字列に限定されている。

これをうまく回避するために、私ならデータをルックアップできる方法をとるだろう。ルックアップのキーには要素の ID が良いだろう。そうすれば、drop イベントで、そのデータを逆参照することができる。

例を見てみよう:

var people = {
  rem : {
    name : "Remy Sharp",
    blog : "http://remysharp.com"
  },
  brucel : {
    name : "Bruce Lawson",
    blog : "http://brucelawson.co.uk"
  }
  // etc...
}

var dragItems = document.querySelectorAll('[draggable=true]');

for (var i = 0; i < dragItems.length; i++) {
  addEvent(dragItems[i], 'dragstart', function (event) {
    // store the ID of the element, and collect it on the drop later on
    event.dataTransfer.setData('Text', this.id);
  });
}

addEvent(drop, 'drop', function (event) {
  // stops the browser from redirecting off to the text.
  if (event.preventDefault) {
    event.preventDefault();
  }

  var person = people[event.dataTransfer.getData('Text')];

  this.innerHTML += '<p><a href="' + person.blog + ">' + person.name + '</a></p>';

  return false;
});

この関連データを伴ったドラッグ・アンド・ドロップの動作デモを見てほしい。 (ソース・コード)

確かにこれが理想的とはいえないが、ドラッグしたデータの中に他のコンテンツ・タイプをセットできるのだ。これによって、将来的には、面白いアプリケーションができるに違いない。

ドラッグ・アイコン

dragstart イベントの他のオプションを使えば、ドラッグ画像もセットすることができるのだ。つまり、あなたのカーソルの下に表示されるものだ。

DOM フラグメントを生成してから、setDragImage(element, x, y) を使って dataTransfer とそのフラグメントを結び付けることができる。

カスタムのドラッグ画像を使うために、これまでの例に次のコードを加えてみよう:

var dragIcon = document.createElement('img');
dragIcon.src = 'http://twivatar.org/twitter/mini';

これはイメージ要素を生成する。そして、dragstart イベントの中に、そのドラッグ画像をどうやってセットするかというと:

addEvent(dragItems[i], 'dragstart', function (event) {
  // store the ID of the element, and collect it on the drop later on
  event.dataTransfer.setData('Text', this.id);
  event.dataTransfer.setDragImage(dragIcon, -10, -10);
  return false;
});

これは、カーソルの下に 10 ピクセルのカスタムのドラッグ・アイコンをセットしたのだ。これを見てほしい:カスタム・イメージを伴ったドラッグ・アンド・ドロップ (ソース・コード)

ネイティブのドラッグ

ドラッグ・アンド・ドロップに対応した JavaScript ライブラリはたくさんある。しかし、私が見たいのは、ライブラリ・ベースにフォールバックできるようなネイティブの DnD サポートなのだ。しかし、jQuery を含めて、いくつかのライブラリは、イベント・ハンドラの中に引き渡されるときに独自のイベントを作るということも知っている。しかし、これは、今のところ、dataTransfer オブジェクトを使うことができないことを意味する。だから、イベントを自分でバインディングせざるを得ないだろう。私は、きっとすぐにこれは変わると思う。

ドラッグ・アンド・ドロップにはもっと多くのことがあるが、今のところはこれで十分なはずだ。

私はどうかというと、ARIA をサポートできるかどうかを調べるために HTML5demos.com のドラッグ・アンド・ドロップのデモをアップデートしているところだ。そして、それが実行されているところをスクリーンキャストにキャプチャするつもりだ。この場所をチェックしてほしい!

もっと読む