🌊

ドラッグ要素のズレを解決する方法: position: absoluteと基準要素

に公開

Webアプリでのドラッグ&ドロップの実装していて、マウスに追従する要素がズレるて沼ったことはないでしょうか。
特に、 position: absoluteの基準要素が誤って設定されていると、この問題が発生しやすいです。
今回はこの問題の原因と、それをどのように解決するかについて、詳細に解説します。

問題の発端: ドラッグ中の要素がズレる

チェスアプリを実装中、駒をドラッグした際に駒がマウスの位置からズレて表示されるという問題が発生しました。
初めは座標計算やoffsetの使い方、remやpxの単位変換が間違っている?と思っていましたが、

原因はposition: absoluteの基準要素が意図せず間違って設定されていたことでした。

ドラッグ要素のズレの原因

position: absolute の基準要素

CSSの position: absolute は、次のように位置を決定するための基準を持っています:

最も近い position: static 以外の親要素 が基準になる。
A positioned element is an element whose computed position value is either relative, absolute, fixed, or sticky. (In other words, it's anything except static)[1]

このため、もしposition: relativeを設定した親要素がドラッグ対象の基準要素になってしまうと、意図しない位置に要素が配置されてしまいます。


DOM 構造(簡略)

<div class="chessboard">
  <div class="tile">
    <div class="piece"></div>
  </div>
</div>

CSSの設定

.chessboard {
  position: relative;
}

.tile {
  position: relative; // ← ドラッグ対象の基準要素
}

JavaScript 側の処理

piece.style.position = "absolute";
piece.style.left = "100px";
piece.style.top = "100px";

ここでは、position: absoluteが指定されているため、駒の位置は.tileを基準に配置されます。
もしtileがrelativeであれば、駒はtile要素を基準に配置され、ズレが発生します。

どうすればズレを解消できるのか?

この問題を解決するためには、次のように基準要素を修正する必要があります。

1. position: fixed

position: fixedを使うことで、ドラッグ中の要素が常に画面全体を基準に配置されます。
これにより、親要素の影響を受けず、マウスの位置に合わせて正しく要素が移動します。

element.style.position = "fixed";
element.style.left = `${e.clientX - SIZE / 2}px`;
element.style.top = `${e.clientY - SIZE / 2}px`;
element.style.pointerEvents = "none";
element.style.zIndex = "1000"; // 必要に応じて z-index を調整

2. ドラッグ中の位置変更

ドラッグ中に要素の位置を更新する際には、画面全体を基準に位置を変更します。

element.style.left = `${e.clientX - SIZE / 2}px`;
element.style.top = `${e.clientY - SIZE / 2}px`;

3. ドロップ時の位置判定

ドロップ時、要素がどの位置にドロップされたかを 親要素基準で計算 します。
ここではgetBoundingClientRect()[2]を使ってボード上の位置を取得します。

const rect = board.getBoundingClientRect();

const x = Math.floor((e.clientX - rect.left) / SIZE);
const y = Math.floor((e.clientY - rect.top) / SIZE);

4. ドラッグ終了後のリセット

ドラッグが終了した時には、要素の位置を相対位置に戻すために、positionをrelativeにリセットします。

element.style.position = "relative";
element.style.removeProperty("top");
element.style.removeProperty("left");
element.style.removeProperty("z-index");
element.style.removeProperty("pointer-events");

まとめ: ドラッグ&ドロップの正しい実装方法

今回の問題のように、ドラッグ&ドロップで要素がズレる原因として最も多いのは、positionの基準要素が正しく設定されていないことです。
次の表に、どのような場面でどのpositionを使うべきかをまとめました。

やりたいこと 適した position
マウスに追従させたい fixed
親要素基準で配置 absolute
通常レイアウト relative

ズレを解消するためのポイント

  • position: fixedは常にビュー全体を基準に位置決めするため、ドラッグ中に最適
  • 親要素がrelativeの場合、absoluteを使うと予期しないズレが発生する
  • ドラッグ終了後は、位置を元に戻す

最後に

position: absoluteは親要素の影響を受けるため、基準要素がどこかを意識しましょう。また、ドラッグ処理を実装する際は、座標系をしっかり統一することが重要です。画面全体を基準にしたい場合、position: fixedを使うと便利です。

脚注
  1. position - CSS | MDN - Types of positioning ↩︎

  2. getBoundingClientRect()は、DOM要素の位置とサイズを取得するためのメソッドです。このメソッドは、要素のビューポート(表示領域)内での位置を基準に、要素の左端、上端、幅、高さをピクセル単位で返します。CSSの変形やスクロールの影響も考慮され、レイアウトが確定した状態で計算されるため、要素の正確な位置やサイズを簡単に取得できます。そのため、マウスイベントの処理や要素配置の調整、可視領域内での要素の検出など、幅広い用途で役立ちます。
    詳しくはこちらを参照 ↩︎

Discussion