ホームページ > 記事 > ウェブフロントエンド > JavaScript のドラッグ アンド ドロップ効果の詳細な研究
ドラッグ アンド ドロップ効果は、ドラッグ アンド ドロップとも呼ばれ、最も一般的な 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);
ドラッグ アンド ドロップ オブジェクトの位置は絶対位置である必要があります:
そして、_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 プログラムで設定します。
コンテナの範囲制限とは、ドラッグ&ドロップで上下左右の範囲を指定することです。
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):
这里的参数是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:
ff:
これは、setCapture の後、ドキュメントのコンテンツをドラッグ アンド ドロップして選択することができないことを意味します。たとえば、ondragstart はシステムのデフォルトのアクションを指します。
ただし、setCapture のパラメータが false の場合でも、コンテナ内のオブジェクトはイベントをトリガーできるため (詳細についてはマウス キャプチャの部分を参照)、setCapture のパラメータを true に設定するか、デフォルト値を維持する必要があります。
たとえば、ctrl+a を使用してコンテンツを選択します。
追記: onkeydown、onkeyup、onkeypress イベントはマウス キャプチャの影響を受けません。
そして、ff はマウスダウン時に元の選択されたコンテンツをクリアできますが、マウスをドラッグして Ctrl+A を押すと引き続きコンテンツが選択されます。
ただし、システムのデフォルトのアクションを破棄した後は、この選択はドラッグ アンド ドロップ操作には影響しません。これは主にエクスペリエンスを向上させるためです。
しかし、この方法では、ドラッグ アンド ドロップ オブジェクト自体が選択されないようにすることしかできません。その後、選択をクリアするより良い方法を見つけました。これは、ドラッグ アンド ドロップ オブジェクトの選択効果に影響を与えないだけでなく、ドキュメント全体もクリアします:
ドラッグ アンド ドロップ処理中にコンテンツが選択されないようにするには、互換性のある記述メソッドを以下に示します:
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 には背景が透明のバグがあります (バグかどうかはわかりません)。次のコードを使用してテストできます:
なぜですか?次に、次のコードを使用してテストします:
この問題は、プログラムでドラッグアンドドロップオブジェクトの背景色を設定するだけで解決できますが、場合によっては透明にする必要がある場合(カット効果など)、どうすればよいですか?
最初に思いつくのは、背景色を追加して完全に透明に設定することですが、そうするとコンテナ内の境界線やオブジェクトまで完全に透明になってしまい、これでは良くありません。
with(this._Handle.appendChild(document.createElement("div")).style ){
width = height = "100%"; backgroundColor = "#fff"; filter = "alpha(opacity:0)";
プログラムにこのバグが発生した場合は、プログラムのオプションのパラメータを設定してくださいTransparent を true に設定すると、そのようなレイヤーが自動的に挿入されます。
もっと良い方法があればアドバイスをお願いします。
今回はここまでですが、考慮されていないiframeやスクロールなどもありますので、必要に応じて検討していきます。