ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript のドラッグ アンド ドロップ効果の詳細な研究

JavaScript のドラッグ アンド ドロップ効果の詳細な研究

黄舟
黄舟オリジナル
2016-12-14 16:05:39814ブラウズ

ドラッグ アンド ドロップ効果は、ドラッグ アンド ドロップとも呼ばれ、最も一般的な JS 特殊効果の 1 つです。
多くの詳細を無視すれば、実装は非常に簡単ですが、多くの場合、詳細が困難になります。
このプログラムのプロトタイプは、画像カットエフェクトを作成していたときに作成されました。当時、いくつかの同様のエフェクトを参照し、muxrwc と BlueDestiny から多くのことを学びました。
整理するたびに気分が良くなりますが、時々、私が学んだ知識と同じように、どこかに改善の余地があること、どこかに間違いがあること、特定のニーズを実現する必要があることに気づくことがあります。

単純なドラッグ アンド ドロップのみが必要な方もいるかもしれないことを考慮して、学習を容易にするために、ドラッグ アンド ドロップの SimpleDrag の簡略化されたバージョンがあります。

プログラム原理

ここでは、SimpleDrag を例として基本原理について説明します。

まず、初期化プログラムにはドラッグ アンド ドロップ オブジェクトが必要です:

this.Drag = $(drag);

また、ドラッグ アンド ドロップに対する相対的なマウスの座標を記録する 2 つのパラメータもあります。先頭にオブジェクトをドロップします:

this._x = this. _y = 0;

イベントを追加および削除するための 2 つのイベント オブジェクト関数もあります:

this._fM = BindAsEventListener(this, this.Move); ._fS = Bind(this, this.Stop);

それぞれプログラムをドラッグ中とプログラムのドラッグを停止しています。

ドラッグ アンド ドロップ オブジェクトの位置は絶対位置である必要があります:

this.Drag.style.position = "absolute";

最後に、ドラッグ アンド ドロップの開始プログラムをドラッグのマウスダウン イベントにバインドします。アンド ドロップ オブジェクト:

addEventHandler(this. Drag, "mousedown", BindAsEventListener(this, this.Start));

ドラッグ アンド ドロップ オブジェクト上でマウスを押すと、Start プログラムがトリガーされます。ここでは、ドラッグ アンド ドロップ オブジェクトに対するマウスの相対座標が記録されます。 :

this._x = oEvent.clientX - this.Drag.offsetLeft; - this.Drag.offsetTop;


そして、_fM ドラッグ プログラムと _fS stop ドラッグ プログラムをそれぞれバインドします。mousemove イベントと Mouseup イベントをドキュメントにバインドします:

addEventHandler(document, "mousemove", this._fM); 、"mouseup"、this._fS);

ドキュメントにバインドすると、ウィンドウ全体で発生したイベントがドキュメント内で有効であることが保証されます。

マウスがドキュメント上で移動すると、移動プログラムがトリガーされます。これは、ドラッグを実装するプログラムです。

ドラッグアンドドロップオブジェクトに設定する必要がある左と上は、マウスの現在の座標値とドラッグ開始時のマウスの相対座標値の差によって取得できます:

this.Drag.style.left = oEvent.clientX - this._x + "px";
this.Drag.style.top = oEvent.clientY - this._y + "px";

最終的にマウスを放した後、Stop プログラムがトリガーされて、ドラッグアンドドロップ。
ここでの主な機能は、スタート プログラムでドキュメントに追加されたイベントを削除することです:

removeEventHandler(document, "mousemove", this._fM);

このようにして、簡単なドラッグ アンド ドロップ プログラムが完成します。その他の拡張機能と詳細について説明します。


ドラッグアンドドロップロック

ロックには、水平ロック(LockX)、垂直ロック(LockY)、完全ロック(Lock)の3種類があります。

これは比較的簡単です。移動でロックされているかどうかを判断し、完全にロックされている場合は直接戻るだけです。

if(!this.LockX){ this.Drag.style.left = ...; }

if(!this.LockY){ this.Drag.style.top = ... }

トリガーオブジェクト


トリガー オブジェクトは、ドラッグ アンド ドロップ プログラムをトリガーするために使用されます。場合によっては、ドラッグ アンド ドロップ オブジェクト全体をトリガーに使用する必要はありません。この場合、トリガー オブジェクトが必要になります。

トリガー オブジェクトを使用した後も、ドラッグ アンド ドロップ オブジェクトは移動されますが、トリガー オブジェクトはドラッグ アンド ドロップをトリガーするために使用されます (一般的な使用法は、トリガー オブジェクトをドラッグ アンド ドロップ オブジェクトに入れることです)。

スコープ制限


範囲制限を設定するには、まず Limit を true に設定する必要があります。範囲制限には固定範囲制限とコンテナ範囲制限の 2 種類があり、主に Move プログラムで設定します。

原理は、比較した値が範囲を超えた場合、ドラッグアンドドロップオブジェクトが設定範囲内に収まるように左と上に設定する値を修正するというものです。

固定範囲制限


コンテナの範囲制限とは、ドラッグ&ドロップで上下左右の範囲を指定することです。

各属性の意味は次のとおりです:

upper (mxTop): 上限;


lower (mxBottom): 左限界;

right (mxRight): left+offsetWidth限界。

範囲が正しく設定されていない場合、上下または左右が同時に範囲を超える可能性があります。範囲パラメータを修正するための修復プログラムがプログラム内にあります。

修復プログラムはプログラムの初期化と開始プログラムで実行されます。修復プログラムの mxRight と mxBottom を修正します。

this.mxRight = Math.max(this.mxRight, this.mxLeft + this.Drag.offsetWidth); this.mxBottom = Math.max(this.mxBottom, this.mxTop + this.Drag.offsetHeight);

ここで、mxLeft+offsetWidth と mxTop+offsetHeight は、それぞれ mxRight と mxBottom の最小範囲値です。


範囲パラメータに従って移動パラメータを修正します:

iLeft = Math.max(Math.min(iLeft, mxRight - this.Drag.offsetWidth), mxLeft);
iTop = Math.max(Math.min(iTop) , mxBottom - this.Drag.offsetHeight), mxTop);

左上側には大きな値を、右下側には小さな値をとります。

容器范围限制

容器范围限制的意思就是把范围限制在一个容器_mxContainer内。 
要注意的是拖放对象必须包含在_mxContainer中,因为程序中是使用相对定位来设置容器范围限制的(如果是在容器外就要用绝对定位,这样处理就比较麻烦了),还有就是容器空间要比拖放对象大,这个就不用说明了吧。 
原理跟固定范围限制差不多,只是范围参数是根据容器的属性的设置的。

当设置了容器,会自动把position设为relative来相对定位:

!this._mxContainer || CurrentStyle(this._mxContainer).position == "relative" || (this._mxContainer.style.position = "relative");

注意relative要在获取offsetLeft和offsetTop即设置_x和_y之前设置,offset才能正确获取值。

由于是相对定位,对于容器范围来说范围参数上下左右的值分别是0、clientHeight、0、clientWidth。

clientWidth和clientHeight是容器可视部分的宽度和高度(详细参考这里)。 
为了容器范围能兼容固定范围的参数,程序中会获取容器范围和固定范围中范围更小的值:

mxLeft = Math.max(mxLeft, 0); 
mxTop = Math.max(mxTop, 0); 
mxRight = Math.min(mxRight, this._mxContainer.clientWidth); 
mxBottom = Math.min(mxBottom, this._mxContainer.clientHeight);

注意如果在程序执行之前设置过拖放对象的left和top而容器没有设置relative,在自动设置relative时会发生移位现象,所以程序在初始化时就执行一次Repair程序防止这种情况。因为offsetLeft和offsetTop要在设置relative之前获取才能正确获取值,所以在Start程序中Repair要在设置_x和_y之前执行。

因为设置相对定位的关系,容器_mxContainer设置过后一般不要取消或修改,否则很容易造成移位异常。

鼠标捕获

我在一个拖放实例中看到,即使鼠标移动到浏览器外面,拖放程序依然能够执行,仔细查看后发现是用了setCapture。 
鼠标捕获(setCapture)是这个程序的重点,作用是将鼠标事件捕获到当前文档的指定的对象。这个对象会为当前应用程序或整个系统接收所有鼠标事件。 
使用很简单:

this._Handle.setCapture();

setCapture捕获以下鼠标事件:onmousedown、onmouseup、onmousemove、onclick、ondblclick、onmouseover和onmouseout。 
程序中主要是要捕获onmousemove和onmouseup事件。 
msdn的介绍中还说到setCapture有一个bool参数,用来设置在容器内的鼠标事件是否都被容器捕获。
容器就是指调用setCapture的对象,大概意思就是: 
参数为true时(默认)容器会捕获容器内所有对象的鼠标事件,即容器内的对象不会触发鼠标事件(跟容器外的对象一样); 
参数为false时容器不会捕获容器内对象的鼠标事件,即容器内的对象可以正常地触发事件和取消冒泡。 
而对于容器外的鼠标事件无论参数是什么都会被捕获, 
可以用下面这个简单的例子测试一下(ie):

 
 

mouseover
 
<script>document.body.setCapture(); </script> 
 

这里的参数是true,一开始body会捕获所有鼠标事件,即使鼠标经过div也不会触发onmousemove事件。 
换成false的话,div就可以捕获鼠标事件,就能触发onmousemove事件了。

拖放结束后还要使用releaseCapture释放鼠标,这个可以放在Stop程序中:

this._Handle.releaseCapture();

setCapture是ie的鼠标捕获方法,对于ff也有对应的captureEvents和releaseEvents方法。 
但这两个方法只能由window来调用,而且muxrwc说这两个方法在DOM2里已经废弃了,在ff里已经没用了。 
不过ff里貌似会自动设置取消鼠标捕获,但具体的情形就不清楚了,找不到一个比较详细的介绍,谁有这方面的资料记得告诉我啊。

下面都是我的猜测,ff的鼠标捕获相当于能自动设置和释放的document.body.setCapture(false)。 
因为我测试下面的程序,发现ie和ff效果是差不多的: 
ie:

 
 

 
<script> <br>document.body.onmousedown=function(){this.setCapture(false)} <br>document.body.onmouseup=function(){this.releaseCapture()} <br>document.onmousemove=function(){aa.innerHTML+=1} <br></script> 
 

ff:

 
 


<script> <br>document.onmousemove=function(){aa.innerHTML+=1} <br></body> <br></html> <br></p>まだ理解できていないことがたくさんあるので、後で勉強します。 <p></p>ff2 ではマウス キャプチャにバグがあることに注意してください。ドラッグ アンド ドロップ オブジェクトの中にテキスト コンテンツがなく、ブラウザの外にドラッグ アンド ドロップすると、キャプチャは失敗します。 <p> <font size='1px'>  などの空のテキストをドラッグ アンド ドロップ オブジェクトに挿入します。これは解決できますが、このバグは ff3 で修正されました。 <br></p>フォーカスが失われた<p></p> 通常の状況では、マウス キャプチャはイベントを正常にキャプチャできますが、ブラウザ ウィンドウのフォーカスが失われた場合、キャプチャは失敗します。 <p>フォーカス損失を引き起こす可能性のある操作には、ウィンドウの切り替え (alt+tab を含む)、アラート、ポップアップ、その他のポップアップ フォームが含まれることを一時的にテストしました。 <br>フォーカスを失った場合、同時に Stop プログラムを実行してドラッグ アンド ドロップを終了する必要があります。ただし、フォーカスを失った場合は、mouseup イベントをキャプチャできません。つまり、_fS をトリガーできません。 <br>幸いなことに、IE にはキャプチャが失敗したときにトリガーされる onlosecapture イベントがあり、この状況では次のように設定できます: <br></p>addEventHandler(this._Handle, "losecapture", this._fS);<p></p> そしてそれを削除します。 Stop プログラム内: <p></p>removeEventHandler(this._Handle, "losecapture", this._fS);<p></p>ただし、ff には同様のメソッドがありませんが、muxrwc は、losecapture を置き換える window.onblur イベントを見つけて、それを設定できます。 Start プログラム内: <p></p>addEventHandler(window, "blur", this._fS);<p></p>Stop プログラム内での削除: <p></p>removeEventHandler(window, "blur", this._fS);<p></p>その ie には、 window.onblur イベントを使用する場合は、losecapture の代わりに window.onblur を使用してください。コードを節約できるのではないでしょうか。 <p>その後、いくつかのテストを行ったところ、基本的に、losecapture をトリガーするあらゆる状況が同時に window.onblur をトリガーし、機能するようであることがわかりました。 <br> そこで、losecapture の代わりに window.onblur を使用するようにプログラムを修正しましたが、テスト後に問題が発生したため、alt+tab を使用して別のウィンドウに切り替えると、ドラッグは続行できますが、この時点でフォーカスが失われるはずであることがわかりました。時間。 <br></p>そこで、テスト コードとプログラム コードを 1 つずつ削除したところ、DTD が使用されている場合、再びフォーカスを取得するまで window.onblur がトリガーされないことがわかりました。 <p>次のコードを使用してテストできます: <br></p><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/ xhtml1-transitional.dtd"> <p><script>window.onblur=function(){alert(1)} </script>

Window.onblur は、他のプログラムに切り替えてから再び切り替えた後にのみトリガーされます。他にも奇妙な状況がいくつかありますが、いずれにしても、IE で window.onblur を使用するのは理想的ではありません。

デフォルトアクション

選択された状態でテキストコンテンツ、接続、画像をドラッグアンドドロップすると、システムのデフォルトアクションがトリガーされます。たとえば、IEで画像上でマウスをドラッグすると、禁止された操作状態になり、ドラッグが発生します。実行するプログラムの削除は失敗します。

ただし、IEでsetCaptureを設定すると、ユーザーインターフェースを介したマウスによるドラッグアンドドロップ操作やコンテンツの選択が禁止されます。

これは、setCapture の後、ドキュメントのコンテンツをドラッグ アンド ドロップして選択することができないことを意味します。たとえば、ondragstart はシステムのデフォルトのアクションを指します。
ただし、setCapture のパラメータが false の場合でも、コンテナ内のオブジェクトはイベントをトリガーできるため (詳細についてはマウス キャプチャの部分を参照)、setCapture のパラメータを true に設定するか、デフォルト値を維持する必要があります。

ffのマウスキャプチャにはこの機能はありませんが、preventDefaultを使用してイベントのデフォルトアクションをキャンセルすることで解決できます:

oEvent.preventDefault();

追記:preventDefaultを使用すると次のような問題が発生すると言われています。マウスアップロスですが、私はFF3にいます。 テストでは何も見つかりませんでした。

選択をクリア

つまり、setCaptureを設定すると、コンテンツの選択が禁止されます。設定前に選択されていたコンテンツはクリアされません。設定後は、他の方法でコンテンツを選択することもできます。

たとえば、ctrl+a を使用してコンテンツを選択します。
追記: onkeydown、onkeyup、onkeypress イベントはマウス キャプチャの影響を受けません。
そして、ff はマウスダウン時に元の選択されたコンテンツをクリアできますが、マウスをドラッグして Ctrl+A を押すと引き続きコンテンツが選択されます。
ただし、システムのデフォルトのアクションを破棄した後は、この選択はドラッグ アンド ドロップ操作には影響しません。これは主にエクスペリエンスを向上させるためです。

以前は、IEのドラッグ&ドロップオブジェクトのonselectstartをfalseに設定し、スタイルMozUserSelect(css: -moz-user-select) を FF で none に設定します。

しかし、この方法では、ドラッグ アンド ドロップ オブジェクト自体が選択されないようにすることしかできません。その後、選択をクリアするより良い方法を見つけました。これは、ドラッグ アンド ドロップ オブジェクトの選択効果に影響を与えないだけでなく、ドキュメント全体もクリアします:

ie: document.selection .empty()

ff: window.getSelection().removeAllRanges()

ドラッグ アンド ドロップ処理中にコンテンツが選択されないようにするには、互換性のある記述メソッドを以下に示します:

window.getSelection().removeAllRanges() : document .selection.empty();

margin

ドラッグ アンド ドロップ オブジェクトにマージンが設定されている場合、ドラッグ アンド ドロップ時に位置がずれるという別の状況もあります (SimpleDrag ドラッグ アンド ドロップでマージンを設定することでテストできます)。オブジェクトをドロップします)。
その理由は、Start プログラムが _x と _y を設定するときにオフセットを使用してそれらを取得し、この値にはマージンが含まれるため、left と top を設定する前にこのマージンを減算する必要があるためです。
ただし、Start プログラムでマージンを削除すると、Move プログラムで範囲制限を設定するときに計算エラーが発生します。
そのため、Start プログラムで値を取得するのが最善です:

this._marginLeft = parseInt(CurrentStyle) (this.Drag) .marginLeft) || 0;
this._marginTop = parseInt(CurrentStyle(this.Drag).marginTop) || 0;

最終的なスタイルのセクションを参照してください。詳細については。

移動プログラムで値を設定します:

this.Drag.style.left = iLeft - this._marginLeft + "px";
this.Drag.style.top = iTop - this._marginTop + "px";

範囲を修正した後にマージンを設定する必要があることに注意してください。そうしないと、位置がずれてしまいます。

【背景が透明のバグ】

IE には背景が透明のバグがあります (バグかどうかはわかりません)。次のコードを使用してテストできます:




< ;div onmousedown="alert(1) " style="border:10px Solid #C4E3FD; width:50px; height:50px;position:absolute;"


html>

背景のクリックによってトリガーされることがわかります。イベントはトリガーできませんが、境界線をクリックするとトリガーできます。

なぜですか?次に、次のコードを使用してテストします:






; /body>


次の 2 つの div は、本文を超える (つまり、赤いボックスを超える) 場合はイベントをトリガーできないことがわかるはずです。
つまり、イベントをトリガーするポイントが体外にあり、背景が透明な場合、トリガーポイントは体外の何もない場所にあると誤解されるため、イベントはトリガーされません。

解決策は、イベントトリガーポイントをボディ内に維持するか、不透明な背景を設定することです。


この問題は、プログラムでドラッグアンドドロップオブジェクトの背景色を設定するだけで解決できますが、場合によっては透明にする必要がある場合(カット効果など)、どうすればよいですか?
最初に思いつくのは、背景色を追加して完全に透明に設定することですが、そうするとコンテナ内の境界線やオブジェクトまで完全に透明になってしまい、これでは良くありません。

私が考えた解決策は、コンテナ内にレイヤーを追加し、コンテナ全体を覆い、背景色と完全な透明度を設定することです:


with(this._Handle.appendChild(document.createElement("div")).style ){
width = height = "100%"; backgroundColor = "#fff"; filter = "alpha(opacity:0)";

}


プログラムにこのバグが発生した場合は、プログラムのオプションのパラメータを設定してくださいTransparent を true に設定すると、そのようなレイヤーが自動的に挿入されます。
もっと良い方法があればアドバイスをお願いします。

今回はここまでですが、考慮されていないiframeやスクロールなどもありますので、必要に応じて検討していきます。

その他の関連記事については、PHP 中国語 Web サイト (www.php.cn) に注目してください。


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。