以下は、ドラッグ&ドロップ操作が行われる時の各段階についての解説です。
この文書で記述されているドラッグ操作は DataTransfer
インターフェイスを使用します。この文書では DataTransferItem
インターフェイスや DataTransferItemList
インターフェイスは説明しません。
draggable 属性
ウェブページにおいては、既定のドラッグ&ドロップの挙動が使われる場合がいくつかあります。文字列の選択範囲、画像、リンクなどのドラッグなどがこれにあたります。画像かリンクがドラッグされた時は、画像もしくはリンク先の URL がドラッグデータとして設定され、ドラッグ操作が始まります。その他の要素は、既定のドラッグ操作が行われるためには選択範囲に含まれていなければなりません。実際の様子を確認するには、ウェブページの一部を選択して、その上でマウスのボタンを押下し、そのまま選択範囲をドラッグしてください。ドラッグ中、選択範囲の内容を半透明で描画した物がマウスポインターに伴って表示されるでしょう。ただしこの挙動は、ドラッグされたデータを加工するイベントリスナーが存在しない場合の、既定のドラッグの挙動によるものです。
HTML では、画像、リンク、選択範囲の上での既定の動作を除くと、他の要素は初期状態ではドラッグできません。 XUL では、すべての要素がドラッグ可能です。
上記以外の他の HTML 要素をドラッグできるようにするには、以下の3つの事をしなくてはなりません。
- ドラッグできるようにしたい要素の
属性の値をdraggable
true
に設定する。
イベントにリスナーを設定し、そのリスナーの中でドラッグデータを設定する。dragstart
- 上記で定義されたリスナーの中で
ドラッグデータを設定する
。
以下は、コンテンツの一部がドラッグできるようにする例です。
<div draggable="true" ondragstart="event.dataTransfer.setData('text/plain', 'この文字列はドラッグができます')">
この文字列はドラッグが<strong>できます</strong>。
</div>
属性を true に設定すると、その要素はドラッグできるようになります。この属性が設定されていない、あるいは false に設定されている場合、その要素をドラッグする事はできず、代わりにテキストが選択されるでしょう。 draggable
属性は画像やリンクを含めてあらゆる要素に設定できます。ただし、画像とリンクについてだけは初期値がtrueとなっていますので、実際にこれらの要素で使う場合は、要素をドラッグできないようにするために draggable
属性に draggable
false
を設定するという場合がほとんどでしょう。
要素がドラッグ可能になった場合、文字列やその要素に含まれている他の要素が、マウスによるクリックやドラッグなどの通常の操作では選択する事ができなくなることに注意してください。ユーザーが文字列を選択するには、通常の操作の代わりに、 Alt キーを押しながらマウスで選択するか、キーボードで操作を行う必要があります。
XUL 要素では、
属性を使う必要はありません。全ての XUL 要素はドラッグ可能です。draggable
<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
と呼ばれるプロパティを持っています(dataTransfer
は DataTransfer
オブジェクトの一つです)。
ドラッグが行われた時には、何がドラッグされているのかを識別するデータが、そのドラッグに対して関連付けられなくてはなりません。例えば、テキストボックスの中の選択された文字列がドラッグされた時は、ドラッグに関連付けられたデータは、文字列それ自体となります。同様に、ウェブページの中のリンクがドラッグされた時は、リンク先の URL がドラッグデータとなります。
ドラッグデータは、データの型(データの形式)と、データの値の、2つの情報を含んでいます。データの形式はタイプ文字列(テキストデータを示すtext/plainなどのような)で指定し、データの値は文字列で指定します。ドラッグが開始された時、あなたはデータを型と値の設定で指定するでしょう。ドラッグが行われている間、dragenterおよびdragoverイベントのイベントリスナーにおいて、あなたはデータの型を、ドロップが許可されているかどうかの判定に使うでしょう。具体例としては、リンクのドロップを受け付けるドロップターゲットは、リンクの型である 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", "http://www.mozilla.org");
dt.setData("text/plain", "http://www.mozilla.org");
これは、3つの異なる型のデータを追加する例です。最初の型の「application/x-bookmark」は独自の形式です。他のアプリケーションはこの形式をサポートしていないでしょうが、同じウェブサイトやアプリケーションの中の領域同士でのドラッグでは、このような独自の形式を利用できます。また、他の型でもデータを提供することで、このような独自形式をサポートしていない他のアプリケーション向けにも、代替の形式でドラッグできるようになります。「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 要素やその他の要素を指定することもできます。フィードバック画像は、その画像が画面上で表示される場合と同じ形・原寸大で生成されます。 setDragImage()
の第2、第3引数は画像を表示するマウスポインターからの相対オフセットです。
文書中に存在しないものをフィードバック画像として使うために、以下の例のようにして、画像や canvas を利用することもできます。
function dragWithCustomImage(event) {
var canvas = document.createElementNS("http://www.w3.org/1999/xhtml","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 の大きさは幅50ピクセル・高さ50ピクセルで、オフセット値はそれぞれの半分の値(各25ピクセル)となっており、画像はマウスポインターの中央に表示されます(マウスポインターが画像の中央に表示されます)。
Using XUL panels as drag images
Gecko 9.0 が必要(Firefox 9.0 / Thunderbird 9.0 / SeaMonkey 2.6)Gecko の開発者(アドオンまたは Mozilla アプリケーションコードのどちらかを開発している人)の場合、 Gecko 9.0 (Firefox 9.0 / Thunderbird 9.0 / SeaMonkey 2.6) は XUL panel
要素をドラッグフィードバック画像として使用することの対応を追加します。 setDragImage()
に panel
要素に渡すだけです。
この XUL panel
を考えてみてください。
<panel id="panel" style="opacity: 0.6">
<description id="pb">Drag Me</description>
</panel>
<vbox align="start" style="border: 1px solid black;" ondragstart="startDrag(event)">
<description>Drag Me</description>
</vbox>
ユーザーが上記の vbox
をクリックしてドラッグを始めると、以下の startDrag()
関数が呼び出されます。
function startDrag(event) {
event.dataTransfer.setData("text/plain", "<strong>Body</strong>");
event.dataTransfer.setDragImage(document.getElementById("panel"), 20, 20);
}
これは "<strong>Body</strong>" という文字列が HTML 形式で入った panel をドラッグ画像として使用します。パネルをテキストエディタ―にドロップすると、 "Body" という文字列がテキスト中のドロップした場所に挿入されます。
ドラッグの種類
ドラッグを行う時の操作には、いくつかの種類があります。 copy
(コピー)はドラッグされているデータが現在の場所からドロップ先の場所にコピーされることを示します。 move
(移動)はドラッグされているデータがドロップ先に移動されることを示し、 link
(リンク)はドラッグ元とドロップ先の場所との間に何らかの形での関連付けや繋がりが作られることを示します。
イベントのリスナーにおいて、 dragstart
effectAllowed
プロパティに値を設定することで、 ドラッグ元について上記の3つの操作のうちどれが許可されているのかを示すことができます。
event.dataTransfer.effectAllowed = "copy";
この例では、コピーのみが許可されています。複数の種類の操作を組み合わせることもできます。
- none
- どの操作も許可されていない(ドロップを禁止)。
- copy
- コピーのみが許可されている。
- move
- 移動のみが許可されている。
- link
- リンクのみが許可されている。
- copyMove
- コピーまたは移動のみが許可されている。
- copyLink
- コピーまたはリンクのみが許可されている。
- linkMove
- リンクまたは移動のみが許可されている。
- all
- コピー、移行、リンクの全ての操作が許可されている。
上に列挙されている値のいずれかと全く等しい値だけが利用可能であることに注意してください。 effectAllowed
プロパティを copyMove
に設定すると、コピーや移動の操作を許可しますが、ユーザーがリンク操作を行うことを防ぐことができます。 effectAllowed
プロパティを変更しない場合、「all」が指定された時と同様に、全ての操作が許可されます。ですので、特定の種類の操作を除外したい場合を除いて、プロパティの値を手動で設定する必要はありません。
ドラッグ操作の間、
または dragenter
イベントのリスナーは、操作が許可されているかどうかを確かめるために dragover
effectAllowed
プロパティを参照できます。これらのイベントにおいて、関連するプロパティである dropEffect
プロパティへ、実際に行われる操作の種類1つだけが指定されるべきです。 dropEffect
プロパティの値として妥当な物は、none
、copy
、move
、またはlink
のみです。このプロパティへは、複数の操作を組み合わせた値は指定できません。
dragenter
および dragover
イベントにおいて、 dropEffect
プロパティはユーザーが要求している操作に初期化されます。ユーザーは操作の種類を修飾キーを押すことにより変更することができます。実際に使用されるキーはプラットフォームごとに異なりますが、大抵の場合は Shift キーと Control キーが、コピー・移動・リンクの各操作の切り替えに使われるでしょう。マウスポインターはどの操作が望まれているのかを示すために、例えばコピーならカーソルの横に「+」記号が表示される、といった風に変化するでしょう。
dragenter
または dragover
イベントの間に dropEffect
プロパティの値を変更すると、ユーザーが選択した操作の種類を上書きし、特定のドロップ操作を強制することができます。この時に指定できる操作の種類は、 effectAllowed
プロパティの値として列挙されている操作に含まれていなくてはならないことに注意してください。それ以外の値を設定した場合は、許可されている操作の中から代わりの値が設定されます。
event.dataTransfer.dropEffect = "copy";
この例では、「コピー」が行なわれる効果です。
その場所へのドロップが禁止されていることを示すために、値として none
を設定することもできます。
drop
および dragend
イベントの中では、 dropEffect
プロパティをチェックすることで最終的に選択されている効果を特定できます。選択された効果が「move」であれば、 dragend
イベントの中でドラッグ元から元のデータを削除するべきです。
ドロップ先の指定
dragenter
および dragover
イベントのリスナーは、ドラッグされている項目がどの場所にドロップされようとしているのかを正確に示す働きをすることが多いです。ウェブページやアプリケーションのほとんどの領域は、ドロップデータを受け取る場所としては不適切です。従って、これらのイベントに対する既定の動作はドロップを禁止する働きをします。
ドロップを許可したい場合、イベントをキャンセルして既定の動作を無効化する必要があります。属性値として定義されたイベントリスナーで false
を返すか、イベントの preventDefault()
メソッドを呼ぶことで、既定の動作を無効にできます。別のファイルに分けられたスクリプトで機能を定義する場合は、後者の方が便利でしょう。
<div ondragover="return false">
<div ondragover="event.preventDefault()">
dragenter
および dragover
イベントのどちらにおいても、 preventDefault()
メソッドを呼び出すと、その場所がドロップ可能な場所であるということを示します。多くの場合は、例えばリンクがドラッグされている時だけなど、特定の状況でのみ preventDefault()
メソッドを呼び出したいと思うでしょう。これを実現するには、条件を確かめて、条件が満たされている時だけイベントをキャンセルするような関数を使って下さい。条件が満たされていない時はイベントをキャンセルしないでおけば、ユーザーがマウスのボタンを放してもその場所へのドロップは行われません。
ドロップを受け付けるか拒絶するかを決める最も一般的な方法は、データ転送の仕組みに含まれているドラッグデータの型を判別するものです。例えば、画像やリンク、もしくはその両方のみを受け付けるといった事ができます。これを実現するには、イベントの dataTransfer
(プロパティ)の types
プロパティを確認します。 types
プロパティはドラッグが開始された時に登録されたタイプ文字列のリストで、最も適切なものから最も適切でないものの順で並んでいます。
function contains(list, value) {
for( var i = 0; i < list.length; ++i ) {
if(list[i] === value) return true;
}
return false;
}
function doDragOver(event) {
var isLink = contains( event.dataTransfer.types, "text/uri-list");
if (isLink) {
event.preventDefault();
}
}
この例では、型のリストの中に text/uri-list 型があるかどうかを確認するために contains
メソッドを使用しています。もし条件が真であれば、イベントはキャンセルされて、ドロップが許可されるでしょう。もしドラッグデータがリンクを含んでいなければ、イベントはキャンセルされず、その場所でのドロップも行われません。
実際に行われる処理の種類をより適切に示すために、 effectAllowed
や dropEffect
プロパティのいずれか、あるいはその両方に値を指定したいと思う事もあるでしょう。当然ですが、イベントをキャンセルするのを忘れると、これらのプロパティの値を変えても何も起こりません。
DataTransfer.types の更新
なお、最新の仕様書では DataTransfer.types
は DOMStringList
ではなく DOMString
の凍結した配列を返すべきだとしています(Firefox 52 以降で対応されました)。
結果として、 contains メソッドはこのプロパティでは動作しなくなりました。特定の種類のデータが提供されているかをチェックするためには、代わりに includes メソッドを使用してください。以下のようなコードを使用します。
if ([...event.dataTransfer.types].includes('text/html')) {
// Do something
}
types
がどちらのメソッドに対応しているかを判別する機能を使用すれば、適切なコードを実行できます。
ドロップのフィードバック
その場所へのドロップが許可されていることをユーザーに示す方法はいくつかあります。マウスポインターは dropEffect
プロパティの値に応じて適切なものに変化します。実際の正確な表示のされ方はユーザーのプラットフォームに依存しますが、通常は例えば「コピー」に対しては「+」記号が表示され、また、ドロップが許可されていない時は「ここにはドロップできません」という意味のアイコンが表示されるでしょう。多くの場合において、このポインターによるフィードバックは十分に役立ちます。
それ以外にも必要に応じて、ユーザーインターフェースを更新して挿入箇所を示したりハイライト表示したりすることもできます。単にハイライト表示するだけであれば、ドロップ対象においてCSSの-moz-drag-over
疑似クラスが利用できます。
.droparea:-moz-drag-over {
border: 1px solid black;
}
この例においてdroparea
クラスの要素は、 dragenter
イベントの中で 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
要素がドロップ対象の領域として使われる事を想定しており、ドラッグされたテキストをドロップ位置に挿入するという効果をもたらします。
ウェブページにおいては、ドロップを受け付けた場合、イベントの preventDefault()
メソッドを呼び出すべきです。これによって、ブラウザ内でのドロップ時の既定の挙動がキャンセルされます。例えば、リンクがウェブページにドロップされた場合、 Firefox はそのリンク先を読み込もうとします。イベントをキャンセルすることで、この動作は抑止されます。
他の形式でデータを取得することもできます。データがリンクであった場合、そのデータは text/uri-list 型でも提供されているでしょう。その場合、リンクを内容に挿入することができます。
function doDrop(event) {
var lines = event.dataTransfer.getData("text/uri-list").split("\n");
for (let line of lines) {
if (line.startsWith("#"))
continue;
let link = document.createElement("a");
link.href = line;
link.textContent = line;
event.target.appendChild(link);
}
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((value) => types.includes(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
イベントの伝搬が終了した後、ドラッグ&ドロップの操作は完了します。
[1] Gecko では、元のノードがドラッグ中(例えばドロップ中や dragover
)に移動したり削除されたりした場合、 dragend
が発行されません。 bug 460801