JavaScriptでHTML要素をドラッグして動かす

※赤い部分をドラッグして動かせます。

<p class="codepen" data-height="265" data-theme-id="default" data-default-tab="js,result" data-user="yoshiharu2580" data-slug-hash="rNaGKBj" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="MovableElement">
  <span>See the Pen <a href="https://codepen.io/yoshiharu2580/pen/rNaGKBj">
  MovableElement</a> by Yoshiharu (<a href="https://codepen.io/yoshiharu2580">@yoshiharu2580</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

パソコン、スマホの両方とも対応しています。

```ts:
interface ElementPosition {
  top: number;
  left: number;
}

interface PointerPosition {
  x: number | null;
  y: number | null;
}

class MovableElement {
  // Elementオブジェクト
  private element?: HTMLElement;
  // 要素の初期位置
  private elementInitialPosition?: ElementPosition;
  // 要素の動き始めの位置
  private elementStartPosition?: ElementPosition;
  // ポインタの動き始めの位置
  private pointerStartPosition: PointerPosition = {
    x: null,
    y: null
  };

  constructor(id: string) {
    this.element = document.getElementById(id);
    this.elementInitialPosition = this.getCurrentPosition();

    this.element.addEventListener("mousedown", (event: MouseEvent) => {
      this.elementStartPosition = this.getCurrentPosition();

      this.pointerStartPosition.x = event.clientX;
      this.pointerStartPosition.y = event.clientY;
    });
    this.element.addEventListener("touchstart", (event: TouchEvent) => {
      this.elementStartPosition = this.getCurrentPosition();

      this.pointerStartPosition.x = event.touches[0].clientX;
      this.pointerStartPosition.y = event.touches[0].clientY;
    });

    window.addEventListener("mousemove", (event: MouseEvent) => {
      if (
        this.pointerStartPosition.x == null ||
        this.pointerStartPosition.y == null
      )
        return;

      const pointerMovedDistanceX = event.clientX - this.pointerStartPosition.x;
      const pointerMovedDistanceY = event.clientY - this.pointerStartPosition.y;
      this.moveDistance(pointerMovedDistanceX, pointerMovedDistanceY);
    });
    window.addEventListener(
      "touchmove",
      (event: TouchEvent) => {
        if (
          this.pointerStartPosition.x == null ||
          this.pointerStartPosition.y == null
        )
          return;

        // touchmove中はスクロールをキャンセルする
        event.preventDefault();

        const pointerMovedDistanceX =
          event.touches[0].clientX - this.pointerStartPosition.x;
        const pointerMovedDistanceY =
          event.touches[0].clientY - this.pointerStartPosition.y;
        this.moveDistance(pointerMovedDistanceX, pointerMovedDistanceY);
      },
      // touchmove中はスクロールをキャンセルする
      { passive: false }
    );

    window.addEventListener("mouseup", () => {
      this.resetPointerStartPosition();
    });
    window.addEventListener("touchend", () => {
      this.resetPointerStartPosition();
    });
  }

  // 要素の位置を取得する
  getCurrentPosition(): ElementPosition {
    const { top, left } = this.element.getBoundingClientRect();
    return { top, left };
  }

  // 要素を現在位置からの距離だけ移動させる
  moveDistance(distanceX: number, distanceY: number) {
    // 補正値
    const correctionX =
      this.elementStartPosition.left - this.elementInitialPosition.left;
    const correctionY =
      this.elementStartPosition.top - this.elementInitialPosition.top;

    // 目標値
    const targetX = correctionX + distanceX;
    const targetY = correctionY + distanceY;

    this.element.style.transform = `translate(${targetX}px, ${targetY}px)`;
  }

  // 要素の動き始めの位置をリセット
  resetPointerStartPosition() {
    if (
      this.pointerStartPosition.x == null ||
      this.pointerStartPosition.y == null
    )
      return;

    this.pointerStartPosition.x = null;
    this.pointerStartPosition.y = null;
  }
}

new MovableElement('element');

```

ここから色々応用できそう。