JavaScript 第10回 ウィンドウをドラッグ＆ドロップで移動（マウス、タッチ両対応）

# はじめに
今回は、第９回で作成したモードレスウィンドウをドラッグ＆ドロップで移動できるようにしたものをタッチにも対応するようにします。
モードレスウィンドウの作成については、[第９回の記事](https://qiita.com/Mount/items/d9dccef62ed22145452c)を参照ください。
第９回の記事でコメントをいただいて、**mouse・・** の代わりに、**pointer・・** が使えるということを伺いましたので、それに対応します。
また、関数名もコメントいただきましたので修正します。

第９回に記載していた「**もう少し改善の余地があることがわかりましたので、そのあたりは今後また記事にしたい** と思います。」については、引き続き保留とし、今回はマウス、タッチ両対応にフォーカスします。

申し訳ありません。
ドラッグ＆ドロップの動作については、すでに第９回の記事に記載していますので、本記事はほぼ書き換えたソースだけの紹介です。

申し訳ありません。
タッチ対応のPCやタブレットを持ち合わせていないため、タッチ対応と記載しながら、タッチの動作については確認できておりませんので、ご容赦ください（mouseeventがタッチで動作するか心配）。

# 今回実施する内容
モードレスウィンドウを **マウス、および、タッチによる** ドラッグ＆ドロップで移動できるようにします。
![dandd.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405480/f8055a91-d94a-6789-1f53-dbe84927f8cd.gif)

# ソースコード(Git Hub)
https://github.com/Build-Mount/JS_10_drag_pointer

# 環境
OS: Windows 11 JP (64bit)
Microsoft Edge：バージョン 120.0.2210.91 (公式ビルド) (64 ビット)

# 参考
[pointerdown](https://developer.mozilla.org/ja/docs/Web/API/Element/pointerdown_event)
[pointermove](https://developer.mozilla.org/ja/docs/Web/API/Element/pointermove_event)
[pointerup](https://developer.mozilla.org/ja/docs/Web/API/Element/pointerup_event)
[pointerleave](https://developer.mozilla.org/ja/docs/Web/API/Element/pointerleave_event)
[MouseEvent: pageX プロパティ](https://developer.mozilla.org/ja/docs/Web/API/MouseEvent/pageX)
[MouseEvent: pageY プロパティ](https://developer.mozilla.org/ja/docs/Web/API/MouseEvent/pageY)
[MouseEvent: offsetX プロパティ](https://developer.mozilla.org/ja/docs/Web/API/MouseEvent/offsetX)
[MouseEvent: offsetY プロパティ](https://developer.mozilla.org/ja/docs/Web/API/MouseEvent/offsetY)

# 用語
なし

# 動作の仕組み
第９回の記事に記載のため省略します。

:::note info
MouseEvent: pageXなどで、ポインターの位置を取得します。これがタッチ動作でも同様か動作確認できておりません。
しかし、[UE_Event](https://developer.mozilla.org/ja/docs/Web/API/UI_Events)にマウス、タッチ共通のUIやタッチ用のUIは存在していないようですので、動作するのではないかと想像しています。
:::

# ウィンドウをドラッグ＆ドロップで移動する具体的な実装
## ソース
第９回の記事とほぼ同じです。
以下の部分だけが異なります。
- ファイル名（単なる区別のため）
- イベント

|第９回|第10回|
|:--|:--|
|`mousemdown`|`pointerdown`|
|`mousemove`|`pointermove`|
|`mouseup`|`pointerup`|
|`mouseleave`|`pointerleave`|
- 関数名（第９回記事でコメントを受けたため修正）
- 関数のコメント（タッチ対応のため文言修正）
 

<details><summary>modeless_drag_pointer.htmlのソースコード（ファイル名のみ変更あり）</summary>

```html:modeless_drag_pointer.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Position</title>
    <link rel="stylesheet" href="modeless_drag_pointer.css" type="text/css">
    <script src="modeless_drag_pointer.js" defer></script>
</head>
<body>
    <div>- 画面表示 -</div>
    <div id="showTop">画面上部表示</div>
    <div id="topDiv">
        <div class="closeCircle" id="closeTop">×</div>
        画面の上に表示されます。
    </div>
</body>
</html>
```
</details>
<details><summary>modeless_drag_pointer.cssのソースコード（変更なし）</summary>

```css:modeless_drag_pointer.css
.closeCircle {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  font-size: 16px;
  line-height: 18px;
  text-align: center;
  position: absolute;
  right: 3px;
  top: 2px;
}

#topDiv .closeCircle {
  background-color: orange;
}

#topDiv {
  visibility: hidden;
  background-color:yellow;
  position: absolute;
  width: 300px;
  height: 100px;
  top: 0px;
  left: 20%;
}
```
</details>

<details><summary>modeless_drag_pointer.jsのソースコード（変更あり）</summary>

```javascript:modeless_drag_pointer.js
/** modeless用の設定 */
document.getElementById("showTop").addEventListener("click", () => {
    showWindow("topDiv");
});

document.getElementById("closeTop").addEventListener("click", () => {
    closeWindow("topDiv");
});

function showWindow(divId) {
    document.getElementById(divId).style.visibility = "visible";
}

function closeWindow(divId) {
    document.getElementById(divId).style.visibility = "hidden";
}

/** ドラッグ＆ドロップで移動する設定*/
var elemId;             //ドラッグした要素のId
var elemOffsetX;        //ドラッグした要素のoffsetX
var elemOffsetY;        //ドラッグした要素のoffsetY

//"topDiv"にマウスダウン、マウスアップ時のEventLisnterを追加。
document.getElementById("topDiv").addEventListener("pointerdown", handlePointerDown);
document.getElementById("topDiv").addEventListener("pointerup", handlePointerUp);

/**
 * マウス、もしくは、タッチで要素がアクティブになった時に、
 * - その座標の保存。
 * - ドラッグによる移動時のEventListnerを追加。×画面の上に表示されます。
 * - ドラッグしながらポインタが画面外移動時のEventListenerを追加。
 * 
 * @param {object} event コールバック関数は発生したイベントを説明するEventに基づくオブジェクト。
 */
function handlePointerDown(event) {
    getElemOffset(event);
    document.getElementById(elemId).addEventListener("pointermove", dragWindow);
    document.body.addEventListener("pointerleave", cancelDragWindow);
}

/**
 * マウス、もしくは、タッチで要素が非アクティブになった時に、
 * - ドラッグによる移動時のEventListenerを削除。
 * 
 * @param {object} event コールバック関数は発生したイベントを説明するEventに基づくオブジェクト。
 */
function handlePointerUp(event) {
    document.getElementById(elemId).removeEventListener("pointermove", dragWindow);
    document.body.removeEventListener("pointerleave", cancelDragWindow);
}

/**
 * 要素のオフセット(offsetX、offsetY)、およびその要素のIdを保存。
 * 
 * @param {object} event コールバック関数は発生したイベントを説明するEventに基づくオブジェクト。
 */
function getElemOffset(event) {
    elemOffsetX = event.offsetX;
    elemOffsetY = event.offsetY;
    elemId = event.target.id;
}

/**
 * マウス、もしくは、タッチで要素をドラッグ時に、ドラッグした要素を移動。
 * 
 * @param {object} event コールバック関数は発生したイベントを説明するEventに基づくオブジェクト。
 */
function dragWindow(event) {
    document.getElementById(elemId).style.left = event.pageX - elemOffsetX + "px";
    document.getElementById(elemId).style.top = event.pageY - elemOffsetY + "px";
}

/**
 * body外移動時のdragWindowのEventListenerを削除。
 * 
 * @param {object} event コールバック関数は発生したイベントを説明するEventに基づくオブジェクト。
 */
function cancelDragWindow(event) {
    document.getElementById(elemId).removeEventListener("pointermove", dragWindow);
    document.body.removeEventListener("pointerleave", cancelDragWindow);
}
```
</details>

# おわりに
今回は、第９回の記事のマウスで「ウィンドウをドラッグ＆ドロップで移動」をタッチにも対応するようにしました。
マウスでの動作は変わりがないことを確認しましたが、タッチは未確認です。
今後こちらのソースをベースに引き続きモーダルウィンドウを作っていこうと思います。