prototype.jsの拡張
既存のprototype.jsにはドラッグの機能はありません。まずドラッグの機能を「Operation.js」ファイルに実装します。prototypeとは原型・見本といった意味がありますが、prototype.jsで定義されているObjectクラスを拡張し新たにOperationクラスを作成します。このクラスは、ドラッグ機能のためのいくつかのプロパティとメソッドを定義して利用することになります。
ドラッグ機能の実装方法
ドラッグ機能を実装するには、次の3つを実装します。
- マウスキーダウン時にオブジェクトを確保する
- キーが押下された状態でマウスが動いたときに、確保しているオブジェクトの座標をマウスの位置と連動して変化させる
- マウスキーアップ時にオブジェクトを解放する
まずは1.について機能を実装していきます。完成したソースコードを示します。
if (!window.Operation) { var Operation = new Object(); } Object.extend(Operation, { DragingObject: null, DragStartX: null, DragStartY: null, //ドラッグ対象のオブジェクトをセット DragStart: function(Target) { Target = $(Target); Operation.DragingObject = Target; Operation.DragStartX = Operation.DragingObject.style.posLeft - event.clientX; Operation.DragStartY = Operation.DragingObject.style.posTop - event.clientY; }, //ドラッグ中 DragItem: function() { if(Operation.DragingObject!=null) { Operation.DragingObject.style.posLeft = Operation.DragStartX + event.clientX; Operation.DragingObject.style.posTop = Operation.DragStartY + event.clientY; } }, //ドラッグ終了 DragEnd: function() { Operation.DragingObject = null; }, //ドラッグ終了時に座標の再セットするメソッド SetPositionProperty: function(element) { $(element).HorizontalNow = $(element).style.posLeft; $(element).VerticalNow = $(element).style.posTop; $(element).HorisontalPosition = $(element).style.posLeft - document.body.scrollLeft; $(element).VerticalPosition = $(element).style.posTop - document.body.scrollTop; Operation.DragingObject = null; Operation.MoveInertia(element); }, //スクロールに連動して浮遊させるメソッド MoveInertia: function(element) { if(Operation.DragingObject!=$(element)) { var Velocity = ($(element).Speed * $(element).Interval) / 1000; $(element).HorizontalDestination = $(element).HorisontalPosition + document.body.scrollLeft; $(element).VerticalDestination = $(element).VerticalPosition + document.body.scrollTop; $(element).HorizontalNow += ($(element).HorizontalDestination - $(element).HorizontalNow) * Velocity; $(element).VerticalNow += ($(element).VerticalDestination - $(element).VerticalNow) * Velocity; if($(element).FloatHorizontal==true) { $(element).style.posLeft = $(element).HorizontalNow; } if($(element).FloatVertical==true) { $(element).style.posTop = $(element).VerticalNow; } setTimeout("Operation.MoveInertia('" + $(element).id + "')",$(element).Interval); } } });
ドラッグ機能と後述する浮遊処理を管理するクラスとして、Operationクラスを定義し、次の3つのプロパティを定義します。
DragingObject: null, DragStartX: null, DragStartY: null,
これらは上から順番に、確保中のオブジェクト、ドラッグ開始時のオブジェクトのX座標、Y座標を表します。
ドラッグするオブジェクトを確保する関数はDragStartメソッドです。このメソッドはMovableFolderをクリックした際に呼び出されます。DragStartメソッドの内部では、まず最初にマウスでクリックされたオブジェクトを変数に代入します。
Operation.DragingObject = event.srcElement;
event.srcElementには、mousedownイベントが発生したオブジェクトが格納されています。このオブジェクトをDragingObject変数へ代入します。
また、ドラッグ開始時のマウスポインタの座標を変数に格納します。この時、現在の座標からクリックしたウィンドウ上の座標を引いた値を格納しておきます。
マウスが動いたときにオブジェクトをドラッグするための関数がDragItemメソッドです。このメソッドの中ではマウスの移動に応じて座標を変更するロジックが実装されています。
まず、ドラッグ中かどうかをDragingObjectに変数が入っているかどうかで判断します。ドラッグ中の場合、ドラッグ対象のオブジェクトの座標を変更します。具体的には、ドラッグ開始時に格納した値に現在のマウスのウィンドウ上の値を足しています。
この計算でドラッグが可能になるのですが、詳細について説明します。問題を簡単にするためにX座標で考えて見ましょう。100の位置に幅10のオブジェクトがあるとします。位置103をクリックするとDragStartXには-3が代入されます。ドラッグを開始して、位置140までマウスを移動させるとDragStartX + event.clientXの値は-3+140でオブジェクトは137の位置に移動します。この辺の計算は単純な座標計算です。DragItemメソッドを実行することにより、オブジェクトをドラッグ可能にすることができます。
最後にmouseupイベントでオブジェクトを解放する処理を書く必要があります。DragEndメソッドの中のOperation.DragingObject = null;の部分がその処理にあたります。他の部分は後述する浮遊処理のためのコードなので、今は気にしないで構いません。
作成したメソッド呼出し部分の作成
作成したクライアントスクリプトを呼び出すためにASP.NETのカスタムコントロールにいくつかの属性を定義する必要があります。修正したOnPreRenderメソッドの内部を示します。
protected override void OnPreRender(EventArgs e) { this.Style.Add("position", "absolute"); this.Style.Add("posTop", this.VerticalPosition.ToString()); this.Style.Add("posLeft", this.HorisontalPosition.ToString()); ///スクリプトへのリンク this.Page.ClientScript.RegisterClientScriptResource( typeof(JavaScript), "Obic.Higuchi.UI.WebForm.JavaScript.Prototype.js"); this.Page.ClientScript.RegisterClientScriptResource( typeof(JavaScript), "Obic.Higuchi.UI.WebForm.JavaScript.Operation.js"); ///追加部分↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ if (this.zCanDrag == true) { ///DragStart this.Page.ClientScript.RegisterStartupScript( typeof(MovableFolder), this.ClientID + "DragStart" , JavaScript.StartScript + "Event.observe($('" + this.ClientID + "')," + " 'mousedown', Operation.DragStart, false);" + JavaScript.EndScript + Environment.NewLine); ///DragItem this.Page.ClientScript.RegisterStartupScript( typeof(MovableFolder) , JavaScript.DragItemKey, JavaScript.StartScript + "Event.observe(document," + " 'mousemove', Operation.DragItem, false);" + JavaScript.EndScript + Environment.NewLine); ///DragEnd this.Page.ClientScript.RegisterStartupScript( typeof(JavaScript), this.ClientID + "DragEnd" , JavaScript.StartScript + "Event.observe(document," + " 'mouseup', Operation.DragEnd , false);" + JavaScript.EndScript + Environment.NewLine); } ///追加部分↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ }
まずは新たに埋め込んだ「Operation.js」ファイルへのリンクのためのコードを追加します。if (this.zCanDrag == true)以下がドラッグ&ドロップ処理をクライアント側で呼び出すためのコードです。一般的にクライアントでのクリックイベントなどに反応するコードを書くには以下のように書くことになります。
MyControl.Attributes.Add("OnClick","alert('メッセージ');");
しかし、この方法だと別の場所でさらにクリックイベントに反応するコードを書くときに、既に設定してある処理を上書きして消してしまう可能性があります。
これを防止するためにprototype.jsのEventクラスのobserveメソッドを利用してイベントリスナーを登録します。具体的にはこのMovableFolderが吐き出すspanタグのmousedownイベントにDragStartメソッドを登録し、documentのmousemoveイベントにDragItemメソッド、documentのmouseupイベントにDragEndメソッドを登録します。
prototype.jsのEventクラスは複数のイベントリスナーを登録可能です。Eventクラスは自動的に複数のEventを管理してくれるので、Eventのバッティングを避けることが可能になります。
浮遊処理の座標計算ロジックについて
ここまで実装すれば、MovableFolderをドラッグで移動させることが可能になります。ここからは浮遊処理の部分について説明していきます。
浮遊する動作を実現するには、コントロールの座標を一定の間隔で変化させていく必要があります。移動していくときに、等速で移動させるとあまり滑らかな動きに見えません。また、滑らかに停止させるには目的地に近づくにしたがって速度を減速していく必要があります。そのための座標計算は「Operation.js」のMoveInertiaメソッドの内部に記述されています。
具体的には水平方向の座標計算であれば
$(element).HorizontalNow += ($(element).HorizontalDestination
- $(element).HorizontalNow) * Velocity;
の部分にあたります。$(element).HorizontalDestination - $(element).HorizontalNowは目的とする座標HorizontalDestinationから現在の座標HorizontalNowを引いているので、これはすなわち現在の座標から目的地までの距離になります。
また、NowX += GoalX - NowXの演算子+=を展開して分かりやすく書くとNowX = NowX + GoalX - NowXとなり、この計算ロジックのままでは一瞬にして現在のコントロールの座標が目的地の座標と等しくなってしまいます。これを防ぐために右辺に1以下の数値をかけています。これにより一瞬で目的地に到着しないように調節しています。
この説明だけだとわかりづらいと思うので例をあげて動作を説明します。例えば、NowX = 100、GoalX = 500として0.1をかけると、1回目の移動処理における移動距離は(500-100)*0.1で40となります。
2回目はNowX = 140、GoalX = 500なので2回目の移動処理における移動距離は(500-140)*0.1で36になります。2回目の移動処理が終わるとNowX = 176の位置に移動します。これを見ると1回目の移動処理よりも2回目の移動処理における移動距離のほうが小さいことが分かります。
この座標計算のロジックにより、目的地に近づくにしたがって移動速度が遅くなり、結果として滑らかに停止するような動きを実現することが可能です。
