「JavaScriptで要素をドラッグして移動する簡単な方法」という記事が初耳だらけだった件

# はじめに

まず↓の記事を見てない方はぜひ見てください！

https://qiita.com/economist/items/d4254209330c11caff04

自分にとってはこの記事には「えっ、ナニコレ！」なテクニックが多く、特に解説もなかったのでいろいろ調べてたら休日が消えてました...

なのでその時間の供養もかねて、自分が知らなかった部分を中心に、僭越ながら元記事の解説を書いてみたいと思います。

ちなみに、以下が元記事のコードそのままを実装したものです。たしかに掲載コードだけで要素がグリグリ動きますね。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="abMeqMV" data-user="www-tacos" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/www-tacos/pen/abMeqMV">
  js-drag-move-original</a> by www-tacos (<a href="https://codepen.io/www-tacos">@www-tacos</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

# 初耳1: `$img`

まずコードのここ

```html
<img id="$img" src="https://js.cx/clipart/ball.svg" width="40" height="40">
<script>
$img.onpointermove = function(event){
```

**「`$`ってことはjQueryか？でも使ってる様子ないよな...なんで要素オブジェクトを参照できてるんだ？`$`に特別な仕様でもあるのか...？」**

調べてみると、実は単純な話で、HTML内でIDをつけた要素はグローバル変数に格納される仕様とのことでした。（2014年の記事...）

https://qiita.com/nakajmg/items/c895105afae95bfa8fae

なので名前は別になんでもよく、またより正確に参照を書くとこうなるわけですね。

```html
<img id="ballSvg" src="https://js.cx/clipart/ball.svg" width="40" height="40">
<script>
window.ballSvg.onpointermove = function(event){
```

# 初耳2: `event.buttons`

次にコードのここ

```js
$img.onpointermove = function(event){
    if(event.buttons){
```

**「PointerEventのプロパティにボタン？押下状態かな？」**

実際に調べてみるとその通りで、どのボタンを押しているかの状態を持つプロパティでした。

https://developer.mozilla.org/ja/docs/Web/API/Pointer_events#ボタンの状態の判断

以下の表は上記ドキュメントの引用ですが、左クリックや右クリックも識別できるみたいです。

機器のボタンの状態|button|buttons
:---:|:---:|:---:
最後のイベント以降、<br>ボタンもタッチ/ペンの接触も変化がなかった|-1|—
ボタンを押さずにマウスを動かした<br>ボタンを押さずにホバー中にペンを動かした|—|0
マウスの左ボタン、タッチ接触、ペン接触|0|1
マウスの中ボタン|1|4
マウスの右ボタン、ペンのバレルボタン|2|2
マウスの X1 （戻る）ボタン|3|8
マウスの X2 （進む）ボタン|4|16
ペンの消しゴムボタン|5|32

元記事だとそのまま条件に渡しているので、`event.buttons`が0（ボタン押下してない状態）以外で後続の処理が動くようになってます。

なのでマウスの右ボタンやホイールボタンでもドラッグできます。

また、いつ押されたかは関係ないので事前に左ボタンを押下した状態でマウスを重ねに行くことでもドラッグできますね。
※ここを動作として切り分けたいなら、やはりpointerdownで開始地点を監視する必要がありそうです

# 初耳3: `setPointerCapture`

最後にコードのここ

```js
        this.setPointerCapture(event.pointerId)
```

これはもう特に予想とかしないですぐにググりましたw

https://developer.mozilla.org/ja/docs/Web/API/Element/setPointerCapture

重要なのはここ

> ポインターデバイスの接触が要素から外れた場合でも、（スクロールやパンなどで）要素がポインターイベントを受信し続けるようにするために使用できます。

つまりマウスをグリグリ動かしてマウスがimg要素から離れてもpointermoveイベントを発生させ続けることができるわけです。

ここで気になるのが

**「そのキャプチャはいつ解除されるの？永遠につかみ続けない？」**

という点ですが、キャプチャは`releasePointerCapture`メソッドを呼べば明示的に解除することができ、またpointerupイベントが発生した時も自動で解除されるようです。

また、

**「じゃあキャプチャしなかったらどうなるの？」**

と思う人もいるかもしれません。なので`setPointerCapture`をコメントアウトしたバージョンも用意しました。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="RwdXQOE" data-user="www-tacos" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/www-tacos/pen/RwdXQOE">
  js-drag-move-no-capture</a> by www-tacos (<a href="https://codepen.io/www-tacos">@www-tacos</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

ボールをドラッグしたあとにマウスを素早く動かしてボールの外に出すと追従が止まると思います。

# その他の知ってたテクニック

上記以外はだいたい知ってたテクニックですが、知識の整理もかねて軽く説明してみます。

## onpointermove

まずここ

```js
$img.onpointermove = function(event){
```

`pointermove`イベントは要素の上でマウスカーソルやスマホのタッチ位置が動いたタイミングで発火されるPointerEvent型のイベントです。

https://developer.mozilla.org/ja/docs/Web/API/Element/pointermove_event

最近はPointerEvent型を使っておけば問題ないと思ってますが、ほかにもMouseEvent型とTouchEvent型があるので、古い記事やドキュメントを読むときに読み替えたり、場合によっては使い分けたりするといいかも？です。

https://qiita.com/zprodev/items/2d7419a842eb45773510

## style

次にここ

```js
        this.style.left     = this.offsetLeft + event.movementX + 'px'
        this.style.top      = this.offsetTop  + event.movementY + 'px'
        this.style.position = 'absolute'
```

これは要素の位置を元の位置からマウスの動き分だけ移動させる処理ですね。

`this.offsetLeft(Top)`は親要素に対するimg要素の相対位置、`event.movementX(Y)`はマウスの移動量です。

https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/offsetLeft

https://developer.mozilla.org/ja/docs/Web/API/MouseEvent/movementX

`this.style.position = 'absolute'` はCSSで `position: absolute;` を指定するのと同じ意味で、`position: relative;`な親要素に対する相対座標で位置を指定するという設定です。

## draggable

最後にここ

```js
        this.draggable      = false
```

draggableは要素がドラッグ可能かどうかを示す属性です。

https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/draggable

上記ドキュメントに

> この属性が設定されなかった場合の既定値は auto であり、ブラウザーの既定のドラッグ動作であることを意味します。テキストの選択範囲、画像、リンクのみがドラッグ可能です。

とあるように画像はデフォルトで`draggable=true`なので、そのままだとドラッグ操作をしたときにブラウザのデフォルトの挙動をとってしまいます。（半透明の画像がマウスカーソル上に表示され、例えばデスクトップで離すとデスクトップに画像がダウンロードされる）

なので明示的にfalseを設定しているというわけです。

# さいごに

いかがでしたか？

個人的には元記事で解説もしてほしかったなぁ～と思いつつ、ないなら自分で書くか！という気持ちで書かせてもらいました。
この記事が自分と同じような疑問を持った方の助けになれたら幸いです。

記事は以上になります。最後まで読んでくださりありがとうございます。