2023年1月25日

座標

要素を移動させるには、座標についてよく知っておく必要があります。

ほとんどの JavaScript メソッドは、2 つの座標系のうちの 1 つを扱います。

  1. ウィンドウに対する相対座標position:fixed に似ており、ウィンドウの左上端から計算されます。
    • これらの座標を clientX/clientY と表記します。このような名前の理由は、後でイベントプロパティを学ぶ際に明らかになります。
  2. ドキュメントに対する相対座標 – ドキュメントルートの position:absolute に似ており、ドキュメントの左上端から計算されます。
    • これらを pageX/pageY と表記します。

ページが先頭までスクロールされ、ウィンドウの左上隅がドキュメントの左上隅と正確に一致している場合、これらの座標は互いに等しくなります。しかし、ドキュメントが移動すると、要素がウィンドウを横切って移動するため、要素のウィンドウに対する相対座標は変化しますが、ドキュメントに対する相対座標は変わりません。

この図では、ドキュメント内の点をとり、スクロール前 (左) とスクロール後 (右) の座標を示します。

ドキュメントがスクロールされた場合

  • pageY – ドキュメントに対する相対座標は変わりません。ドキュメントの先頭(スクロールアウト)から数えられます。
  • clientY – ウィンドウに対する相対座標は変わります(矢印が短くなります)。同じ点がウィンドウの上部に近くなるためです。

要素の座標: getBoundingClientRect

メソッド elem.getBoundingClientRect() は、組み込みの DOMRect クラスのオブジェクトとして elem を囲む最小の長方形のウィンドウ座標を返します。

主な DOMRect プロパティ

  • x/y – ウィンドウに対する長方形の原点の X/Y 座標、
  • width/height – 長方形の幅/高さ (負の値になる可能性があります)。

さらに、派生プロパティがあります。

  • top/bottom – 長方形の上/下端の Y 座標、
  • left/right – 長方形の左/右端の X 座標。

例えば、このボタンをクリックしてウィンドウ座標を確認してください。

ページをスクロールして繰り返すと、ウィンドウに対するボタンの位置が変わるにつれて、ウィンドウ座標(垂直にスクロールする場合は y/top/bottom)も変化することに気づくでしょう。

これが elem.getBoundingClientRect() の出力図です。

ご覧のように、x/ywidth/height は長方形を完全に記述します。派生プロパティは、それらから簡単に計算できます。

  • left = x
  • top = y
  • right = x + width
  • bottom = y + height

注意してください

  • 座標は、10.5 のような小数点以下の数値になる場合があります。これは正常で、内部的にブラウザは計算に小数を使用します。style.left/top を設定するときに丸める必要はありません。
  • 座標は負の値になる場合があります。例えば、ページがスクロールされて elem がウィンドウの上になった場合、elem.getBoundingClientRect().top は負になります。
なぜ派生プロパティが必要なのでしょうか? x/y があるのに、なぜ top/left が存在するのでしょうか。

数学的には、長方形はその開始点 (x,y) と方向ベクトル (width,height) によって一意に定義されます。したがって、追加の派生プロパティは便宜のためのものです。

技術的には、width/height が負の値になる可能性があり、これにより、例えばマウス選択を開始点と終了点を適切にマークして表現できる、"方向付けられた" 長方形を作成できます。

width/height が負の値の場合、長方形は右下隅から始まり、左上に向かって "成長" します。

これが、負の widthheight を持つ長方形です (例えば、width=-200height=-100)

ご覧のように、この場合、left/topx/y と等しくなりません。

ただし、実際には elem.getBoundingClientRect() は常に正の幅/高さを返します。ここで負の width/height について言及するのは、これらの見かけ上重複しているプロパティが実際には重複していない理由を理解していただくためです。

Internet Explorer: x/y をサポートしていません。

Internet Explorer は、歴史的な理由から x/y プロパティをサポートしていません。

そのため、ポリフィル(DomRect.prototype にゲッターを追加)を作成するか、または top/left を使用することができます。これらは、特に elem.getBoundingClientRect() の結果で、正の width/height の場合は常に x/y と同じです。

座標 right/bottom は CSS position プロパティとは異なります

ウィンドウに対する相対座標と CSS の position:fixed の間には、明らかな類似性があります。

しかし、CSS の位置指定では、right プロパティは右端からの距離を意味し、bottom プロパティは下端からの距離を意味します。

上記の図を見ると、JavaScript ではそうではないことがわかります。これらの座標を含め、すべてのウィンドウ座標は左上隅から数えられます。

elementFromPoint(x, y)

document.elementFromPoint(x, y) の呼び出しは、ウィンドウ座標 (x, y) で最も入れ子になっている要素を返します。

構文は次のとおりです

let elem = document.elementFromPoint(x, y);

例えば、以下のコードは、ウィンドウの中央にある要素のタグをハイライトして出力します。

let centerX = document.documentElement.clientWidth / 2;
let centerY = document.documentElement.clientHeight / 2;

let elem = document.elementFromPoint(centerX, centerY);

elem.style.background = "red";
alert(elem.tagName);

ウィンドウ座標を使用するため、要素は現在のスクロール位置によって異なる場合があります。

ウィンドウ外の座標の場合、elementFromPointnull を返します。

document.elementFromPoint(x,y) メソッドは、(x,y) が表示領域内にある場合にのみ機能します。

いずれかの座標が負であるか、ウィンドウの幅/高さを超える場合、null を返します。

以下は、それの確認を怠ると発生する可能性のある一般的なエラーです。

let elem = document.elementFromPoint(x, y);
// if the coordinates happen to be out of the window, then elem = null
elem.style.background = ''; // Error!

「fixed」ポジショニングでの使用

ほとんどの場合、何かを配置するために座標が必要になります。

要素の近くに何かを表示するには、getBoundingClientRect を使用して座標を取得し、次に CSS の positionleft/top (または right/bottom) と共に使用できます。

例えば、以下の関数 createMessageUnder(elem, html) は、elem の下にメッセージを表示します。

let elem = document.getElementById("coords-show-mark");

function createMessageUnder(elem, html) {
  // create message element
  let message = document.createElement('div');
  // better to use a css class for the style here
  message.style.cssText = "position:fixed; color: red";

  // assign coordinates, don't forget "px"!
  let coords = elem.getBoundingClientRect();

  message.style.left = coords.left + "px";
  message.style.top = coords.bottom + "px";

  message.innerHTML = html;

  return message;
}

// Usage:
// add it for 5 seconds in the document
let message = createMessageUnder(elem, 'Hello, world!');
document.body.append(message);
setTimeout(() => message.remove(), 5000);

ボタンをクリックして実行してください。

このコードは、メッセージを左、右、下、に表示するように変更したり、CSS アニメーションを適用して「フェードイン」させたりすることができます。要素のすべての座標とサイズがあるので、それは簡単です。

ただし、重要な点に注意してください。ページがスクロールされると、メッセージはボタンから離れていきます。

その理由は明らかです。メッセージ要素は position:fixed に依存しているため、ページがスクロールしてもウィンドウの同じ場所に残ります。

これを変更するには、ドキュメントベースの座標と position:absolute を使用する必要があります。

ドキュメント座標

ドキュメントに対する相対座標は、ウィンドウではなくドキュメントの左上隅から始まります。

CSS では、ウィンドウ座標は position:fixed に対応し、ドキュメント座標は最上位の position:absolute に似ています。

position:absolutetop/left を使用して、ドキュメントの特定の場所に何かを配置し、ページスクロール中にそこに残るようにすることができます。しかし、最初に正しい座標が必要です。

要素のドキュメント座標を取得するための標準的なメソッドはありません。しかし、記述するのは簡単です。

2 つの座標系は、次の式で接続されています。

  • pageY = clientY + スクロールアウトしたドキュメントの垂直部分の高さ。
  • pageX = clientX + スクロールアウトしたドキュメントの水平部分の幅。

関数 getCoords(elem) は、elem.getBoundingClientRect() からウィンドウ座標を取得し、現在のスクロールを追加します。

// get document coordinates of the element
function getCoords(elem) {
  let box = elem.getBoundingClientRect();

  return {
    top: box.top + window.pageYOffset,
    right: box.right + window.pageXOffset,
    bottom: box.bottom + window.pageYOffset,
    left: box.left + window.pageXOffset
  };
}

上記の例で position:absolute で使用した場合、メッセージはスクロール時に要素の近くに留まります。

変更された createMessageUnder 関数

function createMessageUnder(elem, html) {
  let message = document.createElement('div');
  message.style.cssText = "position:absolute; color: red";

  let coords = getCoords(elem);

  message.style.left = coords.left + "px";
  message.style.top = coords.bottom + "px";

  message.innerHTML = html;

  return message;
}

まとめ

ページ上の任意の点には座標があります。

  1. ウィンドウに対する相対座標 – elem.getBoundingClientRect()
  2. ドキュメントに対する相対座標 – elem.getBoundingClientRect() に現在のページスクロールを加えたもの。

ウィンドウ座標は position:fixed での使用に最適であり、ドキュメント座標は position:absolute でうまく機能します。

両方の座標系には長所と短所があります。CSS の position absolutefixed のように、どちらか一方または両方が必要な場合があります。

タスク

重要度: 5

以下の iframe では、緑色の「フィールド」を持つドキュメントを確認できます。

JavaScript を使用して、矢印で示された角のウィンドウ座標を見つけてください。

便宜上、ドキュメントには小さな機能が実装されています。任意の場所をクリックすると、そこに座標が表示されます。

あなたのコードは、以下のウィンドウ座標を取得するために DOM を使用する必要があります。

  1. 左上の外側の角(これは簡単です)。
  2. 右下の外側の角(これも簡単です)。
  3. 左上の内側の角(少し難しいです)。
  4. 右下の内側の角(いくつかの方法があります。いずれかを選択してください)。

計算した座標は、マウスのクリックによって返される座標と同じである必要があります。

追伸:コードは、要素のサイズや境界線が固定値に拘束されていない場合にも機能する必要があります。

タスクのサンドボックスを開きます。

外側の角

外側の角は、基本的に elem.getBoundingClientRect() から取得するものです。

左上隅 answer1 と右下隅 answer2 の座標

let coords = elem.getBoundingClientRect();

let answer1 = [coords.left, coords.top];
let answer2 = [coords.right, coords.bottom];

左上内側の角

これは、境界線の幅によって外側の角とは異なります。距離を取得する確実な方法は clientLeft/clientTop です。

let answer3 = [coords.left + field.clientLeft, coords.top + field.clientTop];

右下内側の角

この場合、外側の座標から境界線のサイズを減算する必要があります。

CSS の方法を使用することができます。

let answer4 = [
  coords.right - parseInt(getComputedStyle(field).borderRightWidth),
  coords.bottom - parseInt(getComputedStyle(field).borderBottomWidth)
];

別の方法としては、左上隅の座標に clientWidth/clientHeight を追加することもできます。おそらくそちらの方が良いでしょう。

let answer4 = [
  coords.left + elem.clientLeft + elem.clientWidth,
  coords.top + elem.clientTop + elem.clientHeight
];

サンドボックスでソリューションを開きます。

重要度: 5

positionAt(anchor, position, elem)関数を作成してください。この関数は、anchor要素の近くのpositionに応じて、elemの位置を決定します。

positionは、以下の3つの値のいずれかを持つ文字列でなければなりません。

  • "top"elemanchorの真上に配置します。
  • "right"elemanchorのすぐ右に配置します。
  • "bottom"elemanchorの真下に配置します。

この関数は、タスクのソースコードで提供されているshowNote(anchor, position, html)関数内で使用されます。showNote関数は、指定されたhtmlを持つ「ノート」要素を作成し、anchorの近くの指定されたpositionに表示します。

ノートのデモはこちらです。

タスクのサンドボックスを開きます。

このタスクでは、正確な座標を計算するだけで済みます。詳細については、コードを参照してください。

注意: offsetHeightなどのプロパティを読み取るには、要素がドキュメント内にある必要があります。非表示(display:none)またはドキュメント外の要素にはサイズがありません。

サンドボックスでソリューションを開きます。

重要度: 5

前のタスクの解決策を修正して、ノートがposition:fixedの代わりにposition:absoluteを使用するようにしてください。

これにより、ページをスクロールしたときに要素から「逃げる」のを防ぎます。

そのタスクの解決策を開始点としてください。スクロールをテストするには、スタイル<body style="height: 2000px">を追加します。

解決策は実際には非常に簡単です。

  • .noteposition:fixedの代わりにCSSでposition:absoluteを使用します。
  • 座標の章のgetCoords()関数を使用して、ドキュメント相対座標を取得します。

サンドボックスでソリューションを開きます。

重要度: 5

前のタスク要素の近くにノートを表示(絶対配置)を拡張して、positionAt(anchor, position, elem)関数に、elemanchor内に挿入するように教えてください。

positionの新しい値

  • top-outright-outbottom-out – 以前と同じように動作し、elemanchorの上/右/下に挿入します。
  • top-inright-inbottom-inelemanchor内に挿入します。上/右/下の端に貼り付けます。

例えば

// shows the note above blockquote
positionAt(blockquote, "top-out", note);

// shows the note inside blockquote, at the top
positionAt(blockquote, "top-in", note);

結果

ソースコードとして、タスク要素の近くにノートを表示(絶対配置)の解決策を使用してください。

チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善するための提案がある場合は、コメントする代わりにGitHub issueを送信するか、プルリクエストを送信してください。
  • 記事の内容が理解できない場合は、詳しく説明してください。
  • コードを数語挿入するには<code>タグを使用し、複数行の場合は<pre>タグで囲み、10行以上の場合にはサンドボックス(plnkrjsbincodepen…)を使用してください。