2023 年 1 月 25 日

座標

若要移動元素,我們應該熟悉座標。

大多數 JavaScript 方法會處理兩個座標系統之一

  1. 相對於視窗 – 類似於 position:fixed,從視窗頂端/左端計算。
    • 我們會將這些座標表示為 clientX/clientY,這種名稱的理由將在我們研究事件屬性時變得清楚。
  2. 相對於文件 – 類似於文件根目錄中的 position:absolute,從文件頂端/左端計算。
    • 我們會將它們表示為 pageX/pageY

當頁面捲動到最開始,使得視窗的左上角與文件左上角完全重合時,這些座標彼此相等。但文件移動後,元素的視窗相對座標會改變,因為元素在視窗中移動,而文件相對座標則保持不變。

在這個圖片中,我們取文件中的某一點,並展示它在捲動之前(左)和之後(右)的座標

當文件捲動時

  • pageY – 文件相對座標保持不變,它從文件頂部(現在已捲動出去)開始計算。
  • clientY – 視窗相對座標確實改變了(箭頭變短了),因為同一點離視窗頂部更近了。

元素座標:getBoundingClientRect

方法 elem.getBoundingClientRect() 會傳回視窗座標,用於封裝 elem 的最小矩形,作為內建 DOMRect 類別的物件。

主要的 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/top 不等於 x/y

然而,在實務上,elem.getBoundingClientRect() 始終傳回正 width/height,我們在此提到負 width/height 只是為了讓你了解為什麼這些看似重複的屬性實際上並非重複。

Internet Explorer:不支援 x/y

由於歷史原因,Internet Explorer 不支援 x/y 屬性。

因此,我們可以建立一個 polyfill(在 DomRect.prototype 中新增 getter),或僅使用 top/left,因為對於正 width/height,它們始終與 x/y 相同,特別是在 elem.getBoundingClientRect() 的結果中。

坐標 right/bottom 與 CSS 位置屬性不同

視窗相對坐標和 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);

由於它使用視窗坐標,因此元素可能會根據目前的捲動位置而有所不同。

對於視窗外的坐標,elementFromPoint 會傳回 null

方法 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!

使用「固定」定位

我們大多數時候需要座標才能定位某個東西。

若要在某個元素附近顯示某個東西,我們可以使用 `getBoundingClientRect` 取得其座標,然後將 CSS `position` 與 `left/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:absolute` 和 `top/left` 將某個東西放在文件的特定位置,讓它在頁面捲動時仍停留在該處。但我們首先需要正確的座標。

沒有標準的方法可以取得元素的文件座標。但撰寫它很簡單。

兩個座標系統由以下公式連接

  • 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),用來根據 positionelem 定位在 anchor 元素附近。

position 必須是一個字串,包含以下 3 個值中的任何一個

  • "top" – 將 elem 定位在 anchor 正上方
  • "right" – 將 elem 定位在 anchor 的正右方
  • "bottom" – 將 elem 定位在 anchor 正下方

它用於任務原始碼中提供的函式 showNote(anchor, position, html) 內部,該函式會建立一個具有給定 html 的「註解」元素,並將其顯示在 anchor 附近的給定 position

以下是註解的示範

開啟一個沙盒來執行任務。

在這個任務中,我們只需要準確計算座標。詳情請參閱程式碼。

請注意:元素必須在文件中才能讀取 offsetHeight 和其他屬性。隱藏 (display:none) 或不在文件中的元素沒有大小。

在沙盒中開啟解法。

重要性:5

修改 前一個任務 的解決方案,讓備註使用 position:absolute 而不是 position:fixed

這將防止頁面捲動時備註「脫離」元素。

將該任務的解決方案作為起點。若要測試捲動,請新增樣式 <body style="height: 2000px">

解決方案實際上非常簡單

  • 在 CSS 中使用 position:absolute 而不是 position:fixed 作為 .note
  • 使用 getCoords() 函數從 座標 章節取得與文件相關的座標。

在沙盒中開啟解法。

重要性:5

延伸前一個任務 在元素附近顯示備註 (絕對):教導函數 positionAt(anchor, position, elem)elem 插入 anchor 內部。

position 的新值

  • top-outright-outbottom-out - 與之前的工作方式相同,它們將 elem 插入 anchor 的上方/右側/下方。
  • top-inright-inbottom-in - 將 elem 插入 anchor 內部:將其貼到上/右/下緣。

例如

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

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

結果

作為原始碼,採用任務 在元素附近顯示備註 (絕對) 的解決方案。

教學地圖

留言

留言前請先閱讀此內容…
  • 如果您有改進建議 - 請 提交 GitHub 問題 或提交拉取請求,而不是留言。
  • 如果您無法理解文章中的某些內容 - 請詳細說明。
  • 若要插入幾行程式碼,請使用 <code> 標籤,若要插入多行,請將它們包覆在 <pre> 標籤中,若要插入 10 行以上,請使用沙盒 (plnkrjsbincodepen…)