以下は、ドラッグ&ドロップ操作が行われる時の各段階についての解説です。
draggable属性
Webページにおいては、既定のドラッグ&ドロップの挙動が使われる場合がいくつかあります。文字列の選択範囲、画像、リンクなどのドラッグなどがこれにあたります。画像かリンクがドラッグされた時は、画像もしくはリンク先のURLがドラッグデータとしてセットされ、ドラッグ操作が始まります。その他の要素は、既定のドラッグ操作が行われるためには選択範囲に含まれていなければなりません。実際の様子を確認するには、Webページの一部を選択して、その上でマウスのボタンを押下し、そのまま選択範囲をドラッグしてください。ドラッグ中、選択範囲の内容を半透明で描画した物がマウスポインタに伴って表示されるでしょう。ただしこの挙動は、ドラッグされたデータを加工するイベントリスナが存在しない場合の、既定のドラッグの挙動によるものです。
HTMLでは、画像、リンク、選択範囲の上での既定の動作を除くと、他の要素は初期状態ではドラッグできません。全てのXUL要素も同様です。上記以外の他のHTML要素をドラッグできるようにするには、以下の2つの事をしなくてはなりません:
- ドラッグできるようにしたい要素の
draggable
属性の値をtrue
に設定する。 dragstart
イベントにリスナを設定し、そのリスナの中でドラッグデータをセットする。
以下は、ページ内の一部をドラッグ可能にする例です。
<div draggable="true" ondragstart="event.dataTransfer.setData('text/plain', 'このテキストは多分ドラッグされます')"> このテキストは<strong>多分</strong>ドラッグされます。 </div>
draggable
属性をtrueに設定すると、その要素はドラッグできるようになります。draggable
属性が設定されていない、あるいはfalseに設定されている場合、その要素をドラッグする事はできず、代わりにテキストが選択されるでしょう。draggable
属性は画像やリンクを含めてあらゆる要素に設定できます。ただし、画像とリンクについてだけは初期値がtrueとなっていますので、実際にこれらの要素で使う場合は、要素をドラッグできないようにするためにdraggable
属性にfalse
を設定するという場合がほとんどでしょう。
要素がドラッグ可能にされた場合、テキストやその要素に含まれている他の要素は、マウスによるクリックやドラッグなどの通常の操作では選択する事ができなくなることに注意してください。ユーザがテキストを選択するには、通常の操作の代わりに、Altキーを押しながらマウスで選択するか、キーボードで操作を行う必要があります。
XUL要素では、draggable
属性を使う必要はありません。全てのXUL要素はドラッグ可能となっています。
<button label="ドラッグしてください" ondragstart="event.dataTransfer.setData('text/plain', '「ドラッグしてください」ボタン');">
ドラッグ操作の開始
この例では、ondragstart
属性を使って、dragstartイベントのためのリスナが追加されています。
<div draggable="true" ondragstart="event.dataTransfer.setData('text/plain', 'このテキストは多分ドラッグされます')"> このテキストは<strong>多分</strong>ドラッグされます。 </div>
ユーザがドラッグを開始しようとした時、dragstartイベントが発行されます。この例ではdragstartのリスナは、ドラッグされる要素自身に追加されていますが、他の多くのイベントがそうであるようにドラッグイベントもバブリングしますので、より上位の祖先要素でイベントを監視することもできます。dragstartイベントでは、以下で解説している「ドラッグデータ」「フィードバックイメージ」「ドラッグの種類」を設定することができます。ドラッグデータの指定は必須ですが、多くの状況では、フィードバックイメージとドラッグの種類は既定のもので問題ありません。
ドラッグデータ
全てのドラッグイベントは、ドラッグデータを保持するためのdataTransferと呼ばれるプロパティを持っています。
ドラッグが行われた時には、何がドラッグされているのかを識別するデータが、そのドラッグに対して関連付けられなくてはなりません。例えば、テキストボックスの中の選択されたテキストがドラッグされた時は、ドラッグに関連付けられたデータは、テキストそれ自体となります。同様に、Webページの中のリンクがドラッグされた時は、リンク先のURLがドラッグデータとなります。
ドラッグデータは、データの型(データの形式)と、データの値の、2つの情報を含んでいます。データの形式はタイプ文字列(テキストデータを示すtext/plainなどのような)で指定し、データの値は文字列で指定します。ドラッグが開始された時、あなたはデータを型と値のセットで指定するでしょう。ドラッグが行われている間、<font face="Courier New">dragenter</font>および<font face="Courier New">dragover</font>イベントのイベントリスナにおいて、あなたはデータの型を、ドロップが許可されているかどうかの判定に使うでしょう。具体例としては、リンクのドロップを受け付けるドロップターゲットは、リンクの型であるtext/uri-listかどうかを確認するでしょう。dropイベントにおいてはリスナは、この型の情報を元にドラッグされたデータを取得して、ドロップ位置に挿入するでしょう。
型は、text/plainやimage/jpegなどのような、MIME-typeに似た形式の文字列で、独自の型を作ることもできます。広く一般的に使われている型の一覧がドラッグ型のページにあります。
一つのドラッグ操作で、複数の異なる形式のデータを提供できます。この仕組みにより、独自の形式や、その形式のデータを受け取れない要素向けのフォールバック用の形式など、データをより適切な形式で引き渡すことができます。通常、最後のフォールバック先として使われる形式は、text/plain型として表される普通のテキストデータです。このデータは元のテキストの単純な文字列となるでしょう。
データをdataTransferにセットするには、setDataメソッドを使います。このメソッドは、データの型とデータの値の、2つの引数を取ります。例:
event.dataTransfer.setData("text/plain", "ドラッグされたテキスト");
この例では、データの値は「ドラッグされたテキスト」で、形式はtext/plainです。
データは複数の形式で提供できます。これを実現するには、異なる形式を指定してsetDataメソッドを複数回呼び出します。最も適切な形式から始めて、最後に最も単純なフォールバック用の形式を指定します。
var dt = event.dataTransfer; dt.setData("application/x-bookmark", bookmarkString); dt.setData("text/uri-list", "https://www.mozilla.org"); dt.setData("text/plain", "https://www.mozilla.org");
これは、3つの異なる型でデータを追加する例です。最初の型の「application/x-bookmark」は独自の形式です。他のアプリケーションはこの形式をサポートしていないでしょうが、同じWebサイトやアプリケーションの中の領域同士でのドラッグでは、このような独自の形式を利用できます。また、他の型でもデータを提供することで、このような独自形式をサポートしていない他のアプリケーション向けにも、代替の形式でドラッグできるようになります。「application/x-bookmark」型はそのアプリケーションの中ではより使いやすく詳細な情報を提供できますが、他の型で渡されるデータは、単純な1つのURLもしくは文字列となります。
text/uri-listもtext/plainも、この例では同じデータを含んでいます。多くの場合ではこれで問題ありませんが、必ずこうしなければならないというわけではないことに注意してください。
同じ形式で2回データを登録すると、古いデータは新しいデータによって置き換えられますが、データの形式の登録の順番自体は古いデータを登録した時のままになります。
登録したデータはclearDataメソッドによって削除できます。このメソッドは、削除するデータの形式を引数として求めます。
event.dataTransfer.clearData("text/uri-list");
clearDataメソッドの引数によるデータ形式の指定は省略可能です。データの形式が指定されなかった時は、全ての型のデータが削除されます。ドラッグ開始時にデータが1つも登録されなかった場合、もしくは後の処理で全てのデータが削除された場合、ドラッグ操作は発生しません。
データフィードバックイメージの設定
ドラッグが行われた時、ドラッグ元(dragstartイベントが発行された要素)を元にしてOSによって画像が生成され(例えばWindowsでは半透明の画像になります)、ドラッグしている間マウスポインタに伴って表示されます。この画像は自動的に生成されるため、あなたが用意する必要はありません。しかし、setDragImageによって、独自のドラッグ中のフィードバックイメージを指定することができます。
event.dataTransfer.setDragImage(image, xOffset, yOffset);
このメソッドは3つの引数を要求します。第1引数は一般的には画像の要素ですが、canvas要素やその他の要素を指定することもできます。フィードバックイメージは、その画像が画面上で表示される場合と同じ形・原寸大で生成されます。文書中に存在しないものをフィードバックイメージとして使うために、以下の例のようにして、画像やcanvasを利用することもできます:
function dragWithCustomImage(event) { var canvas = document.createElementNS("https://www.w3.org/1999/xhtml", "html: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', 'ドラッグされるデータ'); dt.setDragImage(canvas, 25, 25); }
このテクニックは、canvas要素を使って独自のドラッグ画像を描画する場合に利用できます。
setDragImageメソッドの第2引数と第3引数は、画像を描画する位置の、マウスポインタからの相対的なオフセット値です。この例では、canvasの大きさは幅50ピクセル・高さ50ピクセルで、オフセット値はそれぞれの半分の値(各25ピクセル)となっており、画像はマウスポインタの中央に表示されます(マウスポインタが画像の中央に表示されます)。
ドラッグの種類
ドラッグを行う時の操作には、いくつかの種類があります。「copy<font face="Verdana">(コピー)」</font>
は、ドラッグされているデータが現在の場所からドロップ先の場所にコピーされることを示します。「move<font face="Verdana">(移動)</font>
」はドラッグされているデータがドロップ先に移動されることを示し、「link<font face="Verdana">(リンク)</font>
」はドラッグ元とドロップ先の場所との間に何らかの形での関連付けや繋がりが作られることを示します。
あなたは<font face="Courier New">dragstart</font>イベントのリスナにおいて、effectAllowedプロパティに値を設定することで、 ドラッグ元について上記の3つの操作のうちどれが許可されているのかを示すことができます。
event.dataTransfer.effectAllowed = "copy";
この例では、コピーのみが許可されています。複数の種類の操作を組み合わせることもできます:
- none
- どの操作も許可されていない(ドロップを禁止)。
- copy
- コピーのみが許可されている。
- move
- 移動のみが許可されている。
- link
- リンクのみが許可されている。
- copyMove
- コピーまたは移動のみが許可されている。
- copyLink
- コピーまたはリンクのみが許可されている。
- linkMove
- リンクまたは移動のみが許可されている。
- all
- コピー、移行、リンクの全ての操作が許可されている。
上に列挙されている値のいずれかと全く等しい値だけが利用可能であることに注意してください。effectAllowedプロパティを変更しない場合、「all」が指定された時と同様に、全ての操作が許可されます。ですので、特定の種類の操作を除外したい場合を除いて、プロパティの値を手動で設定する必要はありません。
ドラッグ操作の間、dragenter
またはdragover
イベントのリスナは、操作が許可されているかどうかを確かめるためにeffectAllowedプロパティを参照できます。これらのイベントにおいて、関連するプロパティであるdropEffectプロパティへ、実際に行われる操作の種類1つだけが指定されるべきです。dropEffectプロパティの値として妥当な物は、none
、copy
、move
、またはlink
のみです。このプロパティへは、複数の操作を組み合わせた値は指定できません。
dragenter
およびdragover
イベントにおいて、dropEffectプロパティはユーザが要求している操作に初期化されます。ユーザは操作の種類を修飾キーを押すことにより変更することができます。実際に使用されるキーはプラットフォームごとに異なりますが、大抵の場合はShiftキーとControlキーが、コピー・移動・リンクの各操作の切り替えに使われるでしょう。マウスポインタはどの操作が望まれているのかを示すために、例えばコピーならカーソルの横に「+」記号が表示される、といった風に変化するでしょう。
dragenter
またはdragover
イベントが発生している間において、あるドロップ対象が特定の操作だけをサポートしている場合などのために、effectAllowedとdropEffectの両プロパティとも値を変更することができます。effectAllowedプロパティの値を変更すると、そのドロップ対象の上でどの種類の操作が許可されているのかを示せます。例えばeffectAllowedプロパティをcopyMove
に設定すると、コピー又は移動の操作は許可されますが、リンクの作成はできなくなります。
dropEffectプロパティの値を変更すると、ユーザが選択した操作の種類を上書きし、特定のドロップ操作を強制することができます。この時に指定できる操作の種類は、effectAllowedプロパティの値として列挙されている操作に含まれていなくてはならないことに注意してください。それ以外の値を設定した場合は、許可されている操作の中から代わりの値が設定されます。
event.dataTransfer.effectAllowed = "copyMove"; event.dataTransfer.dropEffect = "copy";
この例では、最終的な操作として指定されている「コピー」は、許可されている操作の中に含まれています。
その場所へのドロップが禁止されていることを示すために、値としてnone
を設定することもできます。
ドロップ対象の明示
dragenter
およびdragover
イベントのリスナは、ドラッグされている項目がどの場所にドロップされようとしているのかを正確に示す働きをすることが多いです。Webページやアプリケーションのほとんどの領域は、ドロップデータを受け取る場所としては不適切です。従って、これらのイベントに対する既定の動作はドロップを禁止する働きをします。
ドロップを許可したい場合、イベントをキャンセルして既定の動作を無効化する必要があります。属性値として定義されたイベントリスナでfalse
を返すか、イベントのevent.preventDefaultメソッドを呼ぶことで、既定の動作を無効にできます。別のファイルに分けられたスクリプトで機能を定義する場合は、後者の方が便利でしょう。
<div ondragover="return false"> <div ondragover="event.preventDefault()">
dragenter
およびdragover
イベントのどちらにおいても、event.preventDefaultメソッドを呼び出すと、その場所がドロップ可能な場所であるということを示します。多くの場合は、例えばリンクがドラッグされている時だけなど、特定の状況でのみevent.preventDefaultメソッドを呼び出したいと思うでしょう。これを実現するには、条件を確かめて、条件が満たされている時だけイベントをキャンセルするような関数を使って下さい。条件が満たされていない時はイベントをキャンセルしないでおけば、ユーザがマウスのボタンを放してもその場所へのドロップは行われません。
ドロップを受け付けるか拒絶するかを決める最も一般的な方法は、データ転送の仕組みに含まれているドラッグデータの型を判別するものです。例えば、画像やリンク、もしくはその両方のみを受け付けるといった事ができます。これを実現するには、dataTransfer
オブジェクトのtypesを確認します。typesはドラッグが開始された時に登録されたタイプ文字列のリストで、並び順は、最も適切な形式から始まって、最終的なフォールバック先となる単純な形式で終わります。
function doDragOver(event) { var isLink = event.dataTransfer.types.contains("text/uri-list"); if (isLink) event.preventDefault(); }
この例では、型のリストの中にtext/uri-list型があるかどうかを確認するためにcontains
メソッドを使用しています。もし条件が真であれば、イベントはキャンセルされて、ドロップが許可されるでしょう。もしドラッグデータがリンクを含んでいなければ、イベントはキャンセルされず、その場所でのドロップも行われません。
実際に行われる処理の種類をより適切に示すために、effectAllowedやdropEffectプロパティのいずれか、あるいはその両方に値を指定したいと思う事もあるでしょう。当然ですが、イベントをキャンセルするのを忘れると、これらのプロパティの値を変えても何も起こりません。
ドロップのフィードバック
その場所へのドロップが許可されていることをユーザに示す方法はいくつかあります。マウスポインタはdropEffectプロパティの値に応じて適切なものに変化します。実際の正確な表示のされ方はユーザのプラットフォームに依存しますが、通常は例えば「コピー」に対しては「+」記号が表示され、また、ドロップが許可されていない時は「ここにはドロップできません」という意味のアイコンが表示されるでしょう。多くの場合において、このポインタによるフィードバックは十分に役立ちます。
それ以外にも必要に応じて、ユーザインターフェースを更新して挿入箇所を示したりハイライト表示したりすることもできます。単にハイライト表示するだけであれば、ドロップ対象においてCSSの-moz-drag-over
疑似クラスが利用できます。
.droparea:-moz-drag-over { border: 1px solid black; }
この例においてdroparea
クラスの要素は、dragenter
イベントの中でevent.preventDefaultメソッドが呼ばれて有効なドロップ対象となっている間、1ピクセルの黒い枠が表示されます。この疑似クラスはdragover
イベントでの状態の変化には反応しませんので、この疑似クラスでの指定を適用させるにはdragenter
イベントをキャンセルしなくてはならない事に注意してください。
より凝った視覚効果のために、例えばドロップが行われる位置に要素を挿入するなど、dragenter
イベントの間に他の操作をすることもできます。この例なら、挿入される要素は、挿入箇所を示すマーカーあるいはドラッグされている要素が新しい位置に挿入された時の状態のプレビューなどとして利用できるでしょう。このような効果は、例えばimageやseparatorを生成して、dragenter
イベントの処理中にドキュメント中に単に挿入するだけで実現できます。
dragover
イベントは、マウスポインタが現在指している要素において発行されます。挿入点のマーカーをdragover
イベントの発行に応じて移動させたいと思うのは自然な欲求でしょう。そのような場合には、他のマウスイベントでマウスポインタの位置を取得するために使われるのと同じ要領で、イベントのclientXとclientYプロパティを利用できます。
最後に、ドラッグ中にマウスポインタが要素の上を離れる時、dragleave
イベントが発行されます。これは挿入点のマーカーやハイライト表示を消すのにちょうどいいタイミングです。このイベントをキャンセルする必要はありません。-moz-drag-over
疑似クラスを使って指定されたハイライト表示やその他の視覚効果は、すべて自動的に消去されます。dragleave
イベントは、ドラッグがキャンセルされた時でも常に発行されますので、このイベントによって、挿入点の消去などを確実に行うことができます。
ドロップの実行
ユーザがマウスのボタンを放した時、ドラッグ&ドロップの操作は終了します。有効なドロップ対象となっている要素の上でマウスのボタンが放された場合、最後のdragenter
とdragover
イベントはキャンセルされて、ドロップが成功し、drop
イベントがそのドロップ対象において発行されます。それ以外の場所でボタンが放された場合は、ドラッグ操作はキャンセルされ、drop
イベントは発行されません。
drop
イベントの間、あなたはドロップされたデータをイベントから取得して、ドロップ位置に挿入することになります。どのドラッグ&ドロップ操作が望まれていたのかは、dropEffectプロパティで判別することができます。
すべてのドラッグ&ドロップ関連のイベントにおいて、イベントのdataTransfer
プロパティはドラッグされた対象に関するデータを保持しています。データの取得にはgetDataメソッドを利用することになるでしょう。
function onDrop(event) { var data = event.dataTransfer.getData("text/plain"); event.target.textContent = data; event.preventDefault(); }
getDataメソッドは、取得したいデータの型を引数として取ります。実行すると、ドラッグ操作の開始時にsetDataメソッドによって登録された値が文字列として返されます。その型に対するデータが存在しない場合は、空文字が返されます。当然ながら、直前のdragover
イベントでの処理においてチェックした時と同様に、あなたはデータの正しい形式が利用可能かどうかを知りたいと思うでしょう。
上記の例では、まずデータを取得し、ドロップ対象の内容テキストとしてそれを挿入しています。これはp
要素やdiv
要素がドロップ対象の領域として使われる事を想定しており、ドラッグされたテキストをドロップ位置に挿入するという効果をもたらします。
Webページにおいては、ドロップを受け付けた場合、あなたはイベントのpreventDefaultメソッドを呼び出すべきです。これによって、ブラウザ内でのドロップ時の既定の挙動がキャンセルされます。例えば、リンクがWebページにドロップされた場合、Firefoxはそのリンク先を読み込もうとします。イベントをキャンセルすることで、この動作は抑止されます。
あなたは他の形式でデータを取得することもできます。データがリンクであった場合、そのデータはtext/uri-list型でも提供されているでしょう。その場合、リンクを内容に挿入することができます。
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(); }
この例は、ドラッグされたデータからリンクを挿入します。名前から想像できる通り、text/uri-list型は実際に複数のURLの改行区切りのリストを含んでいる場合があります。このコードでは、splitを使って文字列を行ごとに分割し、各行に繰り返し処理を行って、それぞれをリンクとして文書中に挿入しています。ナンバー記号(#)で始まる物はコメントとして除外していることに注意してください。
単純な使い方として、リストの中の最初の有効なURLを取得するために、特別な型「URL
」も利用できます。例:
var link = event.dataTransfer.getData("URL");
これによって、コメントの除外などの処理は一切不要になります。しかし、これはリストの中の最初のURLだけしか取得できないという制限があります。
「URL
」型は特別な省略表記用の型で、typesプロパティで取得できる型のリストには列挙されません。
時には、複数の形式をサポートして、そのうち最も適切な形式で提供されたデータを取得したいと思う事もあるでしょう。以下の例では、3つの形式がドロップ対象によってサポートされています。
以下の例は、提供されたデータの中で最も適切なデータを返す例です:
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(); }
この例はFirefox 3で利用可能なJavaScriptの拡張された機能を使って書かれていますが、他の環境でも動作する様に書き換えることもできます。
ドラッグの終了
1回のドラッグ操作が終了すると、dragend
イベントがドラッグ元(dragstart
イベントが発行されるのと同じ要素)において発行されます。このイベントは、ドラッグ操作が成功したかキャンセルされたかに関わらず発行されます。どの操作が行われたのかは、dropEffectプロパティを参照して知ることができます。
dragend
イベントにおいてdropEffectプロパティの値がnone
である場合、ドラッグ操作がキャンセルされたことを意味します。それ以外の場合は、プロパティの値は実際に行われた操作の種類を示します。ドラッグ元はこの情報に基づいて、ドラッグされた項目を「移動」の操作の後に元の場所から削除することができます。mozUserCancelledプロパティの値は、ユーザが(Escapeキーを押すなどして)ドラッグ操作をキャンセルした場合はtrueとなり、不正なドロップ先だった場合などの他の理由でドラッグ操作がキャンセルされた場合や、ドロップに成功した場合はfalseとなります。
ドロップ操作は同じウィンドウの中または他のアプリケーションの上で行われ得ます。いずれの場合も常にdragend
イベントは発行されます。このイベントのscreenXおよびscreenYプロパティの値には、ドロップが行われたときの画面上での座標が設定されます。
dragend
イベントの伝搬が終了した後、ドラッグ&ドロップの操作は完了します。