どうも!かけちまるです!
Webツールを開発するときによく使われるドラッグ&ドロップ機能。
感覚的に操作できるので使用者側からするととても便利だと思います。
しかし、実装は難しいと思いがちですがJavaScriptを使うと案外サクッとできてしまいます。
今回は次のような制作物を作る事を目標にします。
この記事では、
がわかります。
改めてドラッグ&ドロップという動作を切り分けて考えてみましょう。
こんな感じですかね。
で、上記のようなアクションをしたときに処理が発生するイベントハンドラは、
onMouseDown | マウスを押した時に発生 |
---|---|
onMouseUp | 押していたマウス離した時に発生 |
onMouseMove | マウスを動かしている時に発生 |
というようにこの辺りを使えばドラッグ中に要素をz-index
で1番上にしたり、ドロップ可能な要素に重なったときに色を変えたりできそうですね。
次のHTMLとCSSをベースにドラッグ&ドロップ機能を実装していきます。
HTMLコピー<div class="demo"> <div id="droparea" class="droppable"></div> <div id="box"></div> </div>
CSSコピー.demo{ background-color: #f0f0f0; box-sizing: border-box; border: 1px solid #646464; padding: 10px; width: 100%; height: 300px; position: relative; } #droparea { background-color: #e6e6e6; box-sizing: border-box; border: 1px dotted #646464; width: 50%; height: 100%; } #box { cursor: grab; background-color: #000; width: 40px; height: 40px; position: absolute; top: 50%; left: 70%; }
こんな感じになりましたかね。
ドラッグ&ドロップを実装するためのステップは次の通りです。
ドラッグできるようにする
とりあえず、#box
要素をドラッグできるようにします。
で、JavaScriptは次のように書きます。
#box
要素の上でマウスを押した時(1行目)、カーソルの位置を取得したものをmovePosition
関数の引数に渡します。(8行目)movePosition
関数では、#box
要素のstyleにtop
とleft
を設定しています。(12、13行目)mousemove
イベントでマウスが動いた時(21行目)にmouseMove
関数の処理を走らせます。(17行目)
ブラウザ独自のドラッグ&ドロップ機能が発生しないようにdragstart
イベントを潰しておきましょう。(24行目)
JavaScriptコピーconst boxDOM = document.querySelector('#box'); boxDOM.onmousedown = function(event) { boxDOM.style.position = 'absolute'; boxDOM.style.zIndex = 1000; movePosition(event.pageX, event.pageY); // #box要素の位置を決める関数 function movePosition(pageX, pageY) { boxDOM.style.left = pageX + 'px'; boxDOM.style.top = pageY + 'px'; } // マウスを動かした時の処理 function mouseMove(event) { movePosition(event.pageX, event.pageY); } document.addEventListener('mousemove', mouseMove); }; boxDOM.ondragstart = function() { return false; };
マウスを離した時の動作を追加する
①のままだとマウスを離しても#box
要素がついて来てしまうのでそれを解決します。
追記行は背景色を変えています。
mouseup
イベントでマウスを離した時にmousemove
イベントを無効にします。(23行目)
JavaScriptコピーconst boxDOM = document.querySelector('#box'); boxDOM.onmousedown = function(event) { boxDOM.style.position = 'absolute'; boxDOM.style.zIndex = 1000; movePosition(event.pageX, event.pageY); // #box要素の位置を決める function movePosition(pageX, pageY) { boxDOM.style.left = pageX + 'px'; boxDOM.style.top = pageY + 'px'; } // マウスを動かした時の処理 function mouseMove(event) { movePosition(event.pageX, event.pageY); } document.addEventListener('mousemove', mouseMove); // マウスを離した時にmousemoveイベントを解除する document.onmouseup = function() { document.removeEventListener('mousemove', mouseMove); }; }; boxDOM.ondragstart = function() { return false; };
これでドラッグ&ドロップ自体はできたかと思います。
気になる部分の調整
ちょっと気になるところを調整していきましょう。
追記行は背景色を変えています。
#box
要素をドラッグすると必ず左上を掴んでしまうのでドラッグを開始した位置で掴みたい。 ※1 説明#droparea
要素にドラッグした時にbackground-color: #969696;
にしたい。 ※2 説明cursor: grabbing;
にしたい。※1 説明
マウスを押した時にカーソルから#box
要素の左上の部分までの距離を求めます。(10、11行目)
20、21行目で先ほど求めた値を引くことでドラッグを開始した位置で掴むことができます。
※2 説明
まず、ドロップエリアに入ったか判定するためにdropJudge
変数にnull
を入れておきます。(1行目)
そして、カーソルがある位置の最上部要素(今回の場合は.demo
要素)を取得します。(29行目)elementFromPoint
メソッドは対象要素がなければnull
を返します。
それから、最上部要素(.demo
要素)の子要素である#droparea
要素を指定します。(32行目)
これで、カーソルが#droparea
要素の上になければnull
を返すようになります。
あとは、if
文(33~41行目)で#droparea
要素の上にカーソルがあるかないかを判定することで#droparea
要素をbackground-color: #969696;
にすることができます。
JavaScriptコピーlet dropJudge = null; const boxDOM = document.querySelector('#box'); const dropareaDOM = document.querySelector('#droparea'); boxDOM.onmousedown = function(event) { boxDOM.style.cursor = 'grabbing'; let shiftX = event.clientX - boxDOM.getBoundingClientRect().left; let shiftY = event.clientY - boxDOM.getBoundingClientRect().top; boxDOM.style.position = 'absolute'; boxDOM.style.zIndex = 1000; movePosition(event.pageX, event.pageY); // #box要素の位置を決める function movePosition(pageX, pageY) { boxDOM.style.left = pageX - shiftX + 'px'; boxDOM.style.top = pageY - shiftY + 'px'; } // マウスを動かした時の処理 function mouseMove(event) { movePosition(event.pageX, event.pageY); boxDOM.hidden = true; let elemBelow = document.elementFromPoint(event.clientX, event.clientY); boxDOM.hidden = false; let droppableBelow = elemBelow.closest('#droparea'); if (dropJudge != droppableBelow) { if (dropJudge) { notDroppable(dropJudge); } dropJudge = droppableBelow; if (dropJudge) { onDroppable(dropJudge); } } } document.addEventListener('mousemove', mouseMove); // マウスを離した時にmousemoveイベントを解除する document.onmouseup = function() { document.removeEventListener('mousemove', mouseMove); boxDOM.style.cursor = 'grab'; }; }; function onDroppable(element) { element.style.background = '#969696'; } function notDroppable(element) { element.style.background = '#e6e6e6'; } boxDOM.ondragstart = function() { return false; };
完成
これでマウスイベントでドラッグ&ドロップ機能の基本はできたかと思います。
完成コードはこちらに他になります。
HTMLコピー<div class="demo"> <div id="droparea" class="droppable"></div> <div id="box"></div> </div>
CSSコピー.demo{ background-color: #f0f0f0; box-sizing: border-box; border: 1px solid #646464; padding: 10px; width: 100%; height: 300px; position: relative; } #droparea { background-color: #e6e6e6; box-sizing: border-box; border: 1px dotted #646464; width: 50%; height: 100%; } #box { cursor: grab; background-color: #000; width: 40px; height: 40px; position: absolute; top: 50%; left: 70%; }
JavaScriptコピーlet dropJudge = null; const boxDOM = document.querySelector('#box'); const dropareaDOM = document.querySelector('#droparea'); boxDOM.onmousedown = function(event) { boxDOM.style.cursor = 'grabbing'; let shiftX = event.clientX - boxDOM.getBoundingClientRect().left; let shiftY = event.clientY - boxDOM.getBoundingClientRect().top; boxDOM.style.position = 'absolute'; boxDOM.style.zIndex = 1000; movePosition(event.pageX, event.pageY); // #box要素の位置を決める function movePosition(pageX, pageY) { boxDOM.style.left = pageX - shiftX + 'px'; boxDOM.style.top = pageY - shiftY + 'px'; } // マウスを動かした時の処理 function mouseMove(event) { movePosition(event.pageX, event.pageY); boxDOM.hidden = true; let elemBelow = document.elementFromPoint(event.clientX, event.clientY); boxDOM.hidden = false; let droppableBelow = elemBelow.closest('#droparea'); if (dropJudge != droppableBelow) { if (dropJudge) { notDroppable(dropJudge); } dropJudge = droppableBelow; if (dropJudge) { onDroppable(dropJudge); } } } document.addEventListener('mousemove', mouseMove); // マウスを離した時にmousemoveイベントを解除する document.onmouseup = function() { document.removeEventListener('mousemove', mouseMove); boxDOM.style.cursor = 'grab'; }; }; function onDroppable(element) { element.style.background = '#969696'; } function notDroppable(element) { element.style.background = '#e6e6e6'; } boxDOM.ondragstart = function() { return false; };
おわり
フィードバックを送信
記事についてのフィードバックはTwitterかお問い合わせフォームから受け付けております。