使用 CSS 錨點定位

CSS 錨點定位模組定義了一些功能,允許你將元素相互繫結。元素可以被定義為錨點元素錨點定位元素。錨點定位元素可以繫結到錨點元素上。然後,錨點定位元素的大小和位置可以相對於其繫結的錨點元素的大小和位置進行設定。

CSS 錨點定位還提供了純 CSS 機制來為一個錨點定位元素指定多個備選位置。例如,如果一個工具提示框錨定到一個表單欄位,但在其預設位置設定下會渲染到螢幕外,瀏覽器可以嘗試在另一個建議的位置渲染它,以便它能顯示在螢幕上,或者,如果需要,也可以完全隱藏它。

本文解釋了錨點定位的基本概念,以及如何基礎地使用該模組的關聯、定位和尺寸調整功能。對於下面討論的每個概念,我們都包含了指向參考頁面的連結,其中包含額外的示例和語法細節。有關指定備選位置和隱藏錨點定位元素的資訊,請參閱溢位時的回退選項和條件隱藏指南。

基本概念

將一個元素繫結或關聯到另一個元素的需求非常普遍。例如:

  • 顯示在表單控制元件旁邊的錯誤資訊。
  • 出現在 UI 元素旁邊的工具提示或資訊框,以提供更多相關資訊。
  • 可以訪問的設定或選項對話方塊,用於快速配置 UI 元素。
  • 出現在關聯的導航欄或按鈕旁邊的下拉選單或彈出選單。

現代介面經常需要將某些內容(通常是可重用且動態生成的)相對於一個錨點元素進行定位。如果被繫結的元素(即錨點元素)總是在 UI 中的相同位置,並且被繫結的元素(即錨點定位元素,或簡稱為定位元素)總能在源順序中緊隨其前或其後,那麼建立這樣的用例將相當簡單。然而,事情很少這麼簡單。

定位元素相對於其錨點元素的位置需要隨著錨點元素的移動或配置變化(例如,透過滾動、改變視口大小、拖放等)而保持和調整。例如,如果一個元素(如表單欄位)靠近視口邊緣,其工具提示可能會出現在螢幕外。通常,你希望將工具提示繫結到其表單控制元件,並確保只要表單欄位可見,工具提示就完全保持在螢幕上,必要時自動移動工具提示。你可能已經注意到,這在你桌面或筆記型電腦上右鍵單擊(Ctrl + 單擊)上下文選單時是作業系統的預設行為。

歷史上,將一個元素與另一個元素關聯,並根據錨點的位置動態改變定位元素的位置和大小需要 JavaScript,這增加了複雜性和效能問題。而且,也無法保證在所有情況下都能正常工作。CSS 錨點定位模組中定義的功能使得能夠用 CSS(和 HTML)以高效能和宣告式的方式實現這些用例,而無需使用 JavaScript。

關聯錨點和定位元素

要將一個元素與一個錨點關聯起來,你需要首先宣告哪個元素是錨點,然後指定哪個定位元素要與該錨點關聯。這就在兩者之間建立了一個錨點引用。這種關聯可以透過 CSS 顯式建立,也可以隱式建立。

顯式 CSS 錨點關聯

要用 CSS 將一個元素宣告為錨點,你需要透過 anchor-name 屬性為其設定一個錨點名稱。錨點名稱必須是一個 <dashed-ident>。在這個例子中,我們還將錨點的 width 設定為 fit-content 以獲得一個小方塊錨點,這樣能更好地展示錨定效果。

css
.anchor {
  anchor-name: --my-anchor;
  width: fit-content;
}

將一個元素轉換為錨點定位元素需要兩步:它需要使用 position 屬性進行絕對或固定定位。然後,將定位元素的 position-anchor 屬性設定為錨點元素的 anchor-name 屬性的值,以將兩者關聯起來。

css
.infobox {
  position: fixed;
  position-anchor: --my-anchor;
}

我們將把上面的 CSS 應用到下面的 HTML 中:

html
<div class="anchor">⚓︎</div>

<div class="infobox">
  <p>This is an information box.</p>
</div>

這將渲染如下:

錨點和資訊框現在已經關聯起來了,但目前你只能相信我們的話。它們還沒有被繫結在一起——如果你定位錨點並將其移動到頁面的其他地方,它會自己移動,而資訊框會留在原地。當我們在基於錨點位置定位元素中看到實際的繫結效果時,你就會明白了。

隱式錨點關聯

在某些情況下,由於兩個元素之間語義上的關係,它們之間會建立一個隱式的錨點引用:

  • 當使用 Popover API 將一個彈出框與一個控制元件關聯時,兩者之間會建立一個隱式的錨點引用。這可能發生在:
    • 使用 popovertargetid 屬性,或 commandforid 屬性,以宣告方式將彈出框與控制元件關聯。
    • 使用 source 選項,以程式設計方式將一個彈出框操作(如 showPopover())與一個控制元件關聯。
  • 一個 <select> 元素及其下拉選擇器透過 appearance 屬性的 base-select 值啟用了可自定義 select 元素功能。在這種情況下,兩者之間會建立一個隱式的彈出框-呼叫者關係,這也意味著它們將有一個隱式的錨點引用。

注意: 上述方法將錨點與一個元素關聯起來,但它們尚未被繫結。要將它們繫結在一起,定位元素需要相對於其錨點進行定位,這是用 CSS 完成的。

移除錨點關聯

如果你希望移除先前在錨點元素和定位元素之間建立的顯式錨點關聯,你可以執行以下操作之一:

  1. 將錨點的 anchor-name 屬性值設定為 none,或者設定為一個不同的 <dashed-ident>(如果你想讓另一個元素錨定到它)。
  2. 將定位元素的 position-anchor 屬性設定為當前文件中不存在的錨點名稱,例如 --not-an-anchor-name

然而,在隱式錨點關聯的情況下,你需要使用第二種方法——第一種方法不起作用。這是因為關聯是內部控制的,你無法透過 CSS 移除 anchor-name

例如,要阻止一個可自定義 <select> 元素的選擇器錨定到 <select> 元素本身,你可以使用以下規則:

css
::picker(select) {
  position-anchor: --not-an-anchor-name;
}

相對於錨點定位元素

正如我們上面所見,將一個定位元素與一個錨點關聯起來本身並沒有太大用處。我們的目標是將定位元素相對於其關聯的錨點元素進行放置。這可以透過在內邊距屬性上設定一個 CSS anchor() 函式值、指定一個 position-area,或者使用 anchor-center 放置值將定位元素居中來實現。

注意: CSS 錨點定位還提供了指定回退位置的機制,以防定位元素的預設位置導致其溢位視口。詳情請參閱回退選項和條件隱藏指南。

注意: 錨點元素必須是一個可見的 DOM 節點,關聯和定位才能起作用。如果它被隱藏(例如透過 display: none),定位元素將相對於其最近的已定位祖先進行定位。我們在使用 position-visibility 進行條件隱藏中討論了當錨點消失時如何隱藏錨點定位元素。

使用帶有 anchor() 函式值的內邊距屬性

傳統的絕對定位和固定定位元素是透過在內邊距屬性上設定 <length><percentage> 值來明確地定位的。對於 position: absolute,這個內邊距位置值是相對於最近的已定位祖先的邊緣的絕對距離。對於 position: fixed,內邊距位置值是相對於視口的絕對距離。

CSS 錨點定位改變了這種模式,使得錨點定位元素可以相對於其關聯錨點的邊緣進行放置。該模組定義了 anchor() 函式,它是每個內邊距屬性的有效值。使用時,該函式透過定義錨點元素、定位元素相對於錨點元素的哪一側以及與該側的距離,將內邊距位置值設定為相對於錨點元素的絕對距離。

函式元件如下所示:

anchor(<anchor-name> <anchor-side>, <fallback>)
<anchor-name>

你希望將元素的一側定位到其相對的錨點元素的 anchor-name 屬性值。這是一個 <dashed-ident> 值。如果省略,將使用元素的預設錨點。這是在其 position-anchor 屬性中引用的錨點,或透過非標準的 anchor HTML 屬性與元素關聯的錨點。

注意: 指定一個 <anchor-name> 會將元素相對於該錨點進行定位,但不會提供元素關聯。雖然你可以透過在同一元素上的不同 anchor() 函式中指定不同的 <anchor-name>,將元素的兩側相對於多個錨點進行定位,但定位元素只與單個錨點關聯。

<anchor-side>

指定相對於錨點的一側或多側的位置。有效值包括錨點的 center、物理側(topleft 等)或邏輯側(startself-end 等),或在 anchor() 設定的內邊距屬性軸的起始(0%)和結束(100%)之間的 <percentage>。如果使用的值與設定 anchor() 函式的內邊距屬性不相容,則使用回退值。

<fallback>

一個 <length-percentage>,定義了在元素未進行絕對或固定定位、使用的 <anchor-side> 值與設定 anchor() 函式的內邊距屬性不相容,或錨點元素不存在時用作回退值的距離。

anchor() 函式的返回值是一個根據錨點位置計算出的長度值。如果你直接在錨點定位元素的內邊距屬性上設定長度或百分比,它的定位方式就好像它沒有繫結到錨點元素一樣。這與 <anchor-side> 值與設定它的內邊距屬性不相容並使用回退值時的行為相同。這兩個宣告是等效的:

css
bottom: anchor(right, 50px);
bottom: 50px;

兩者都會將定位元素放置在元素最近的已定位祖先(如果有)或初始包含塊底部的上方 50px 處。

你將使用的最常見的 anchor() 引數會引用預設錨點的一側。你通常也會新增一個 margin 來在錨點和定位元素的邊緣之間建立間距,或者在 calc() 函式中使用 anchor() 來新增該間距。

例如,這條規則將定位元素的右邊緣與錨點元素的左邊緣對齊,然後新增一些 margin-left 來在邊緣之間建立一些空間:

css
.positionedElement {
  right: anchor(left);
  margin-left: 10px;
}

anchor() 函式的返回值是一個長度。這意味著你可以在 calc() 函式中使用它。這條規則將定位元素的邏輯塊結束邊緣定位在距離錨點元素的邏輯塊起始邊緣 10px 的位置,使用 calc() 函式新增間距,這樣我們就不需要新增外邊距了:

css
.positionedElement {
  inset-block-end: calc(anchor(start) + 10px);
}

anchor() 示例

讓我們來看一個 anchor() 的實際例子。我們使用了與之前示例相同的 HTML,但在其上方和下方放置了一些填充文字,以使其內容溢位容器併產生滾動。我們還會給錨點元素與之前示例中相同的 anchor-name

css
.anchor {
  anchor-name: --my-anchor;
}

資訊框透過錨點名稱與錨點關聯,並被賦予固定定位。透過包含 inset-block-startinset-inline-start 屬性(在水平從左到右的書寫模式中,它們分別等同於 topleft),我們將其繫結到了錨點上。我們給資訊框添加了一個 margin,以在定位元素和其錨點之間增加空間:

css
.infobox {
  position-anchor: --my-anchor;
  position: fixed;
  inset-block-start: anchor(end);
  inset-inline-start: anchor(self-end);
  margin: 5px 0 0 5px;
}

讓我們更詳細地看一下內邊距屬性定位的宣告:

  • inset-block-start: anchor(end):這會將定位元素的塊起始邊緣設定為錨點的塊結束邊緣,使用 anchor(end) 函式計算。
  • inset-inline-start: anchor(self-end):這會將定位元素的內聯起始邊緣設定為錨點的內聯結束邊緣,使用 anchor(self-end) 函式計算。

這給了我們以下結果:

定位元素位於錨點元素下方 5px 和右側 5px 的位置。如果你上下滾動文件,定位元素會保持其相對於錨點元素的位置——它被固定到錨點元素,而不是視口。

設定 position-area

position-area 屬性提供了一種替代 anchor() 函式來相對於錨點定位元素的方法。position-area 屬性基於一個 3x3 的網格概念,其中錨點元素是中心單元格。position-area 屬性可用於將錨點定位元素放置在九個單元格中的任何一個,或者使其跨越兩個或三個單元格。

The position-area grid, as described below

網格瓷磚被分為行和列。

  • 三行由物理值 topcenterbottom 表示。它們也有邏輯等效值,如 startcenterend,以及座標等效值,如 y-startcentery-end
  • 三列由物理值 leftcenterright 表示。它們也有邏輯等效值,如 startcenterend,以及座標等效值,如 x-startcenterx-end

中心單元格的尺寸由錨點元素的包含塊定義,而中心單元格與網格外邊緣之間的距離由定位元素的包含塊定義。

position-area 屬性值由一到兩個基於上述行和列的值組成,並提供跨越選項來定義元素應定位的網格區域。

例如

你可以指定兩個值,將定位元素放置在特定的網格單元中。例如:

  • top left(邏輯等效為 start start)會將定位元素放置在左上角的單元格中。
  • bottom center(邏輯等效為 end center)會將定位元素放置在底部中央的單元格中。

你可以指定一個行或列的值,加上一個 span-* 值。第一個值指定了放置定位元素的行或列,初始時將其放置在中心,另一個值指定了要跨越的列的數量。例如:

  • top span-left 會使定位元素被放置在頂行,並跨越該行的中心和左側單元格。
  • y-end span-x-end 會使定位元素被放置在 y 軸的末端,並跨越該列的中心和 x 軸末端的單元格。
  • block-end span-all 會使定位元素被放置在塊結束行,並跨越該行的內聯起始、中心和內聯結束單元格。

如果你只指定一個值,效果會因設定的值而異:

  • 物理側邊值(topbottomleftright)或座標值(y-starty-endx-startx-end)的作用如同另一個值是 span-all。例如,top 的效果與 top span-all 相同。
  • 邏輯側邊值(startend)的作用如同另一個值被設定為相同的值;例如,start 的效果與 start start 相同。
  • center 值的作用如同兩個值都設定為 center(即 center center)。

注意: 有關所有可用值的詳細描述,請參閱 <position-area> 值參考頁面。將邏輯值與物理值混合使用將使宣告無效。

讓我們來演示一些這樣的值;這個例子使用了與前一個例子相同的 HTML 和基礎 CSS 樣式,只是我們增加了一個 <select> 元素,以便能夠更改定位元素的 position-area 值。

資訊框被賦予固定定位,並使用 CSS 與錨點關聯。載入時,它被設定為透過 position-area: top; 繫結到錨點,這使其位於位置區域網格的頂部。一旦你從 <select> 選單中選擇不同的值,這個設定將被覆蓋。

css
.infobox {
  position: fixed;
  position-anchor: --my-anchor;
  position-area: top;
}

我們還包含了一個簡短的指令碼,用於將從 <select> 選單中選擇的新 position-area 值應用到資訊框:

js
const infobox = document.querySelector(".infobox");
const selectElem = document.querySelector("select");

selectElem.addEventListener("change", () => {
  const area = selectElem.value;

  // Set the position-area to the value chosen in the select box
  infobox.style.positionArea = area;
});

嘗試從 <select> 選單中選擇新的 position-area 值,看看它們對資訊框位置的影響:

定位元素的寬度

在上面的示例中,我們沒有在任何維度上顯式地設定定位元素的尺寸。我們故意省略了尺寸設定,以便讓你觀察到由此產生的行為。

當一個定位元素被放置到沒有顯式尺寸的 position-area 網格單元中時,它會與指定的網格區域對齊,其行為就像 width 被設定為 max-content。它的尺寸是根據其包含塊的大小來確定的,即其內容的寬度。這個尺寸是由設定 position: fixed 強加的。自動尺寸的絕對定位和固定定位元素會自動調整大小,伸展到足以容納文字內容,同時受視口邊緣的限制。在這種情況下,當使用任何 leftinline-start 值放置在網格的左側時,文字會換行。如果錨定元素的 max-content 尺寸比其錨點更窄或更短,它們不會增長以匹配錨點的大小。

如果定位元素是垂直居中的,例如使用 position-area: bottom center,它將與指定的網格單元對齊,並且寬度將與錨點元素相同。在這種情況下,它的最小高度是錨點元素的包含塊大小。它不會溢位,因為 min-widthmin-content,這意味著它至少會和它最長的單詞一樣寬。

使用 anchor-center 在錨點上居中

雖然你可以使用 position-areacenter 值來居中錨點定位元素,但內邊距屬性結合 anchor() 函式可以更精確地控制位置。CSS 錨點定位提供了一種方法,當使用內邊距屬性(而不是 position-area)進行繫結時,可以相對於其錨點居中一個錨點定位元素。

justify-selfalign-selfjustify-itemsalign-items 屬性(以及它們的簡寫 place-itemsplace-self)的存在是為了讓開發者能夠在各種佈局系統中輕鬆地在內聯或塊方向上對齊元素,例如在 flex 子元素的情況下沿著主軸或交叉軸。CSS 錨點定位為這些屬性提供了一個額外的值,anchor-center,它將一個定位元素與其預設錨點的中心對齊。

這個例子使用了和前一個例子相同的 HTML 和基礎 CSS。資訊框被賦予了固定定位,並繫結到錨點的底部邊緣。然後使用 justify-self: anchor-center 來確保它在錨點的中心水平居中。

css
.infobox {
  position: fixed;
  position-anchor: --my-anchor;
  top: calc(anchor(bottom) + 5px);
  justify-self: anchor-center;
}

這將錨點定位元素在其錨點底部居中:

基於錨點大小調整元素尺寸

除了相對於錨點的位置來定位元素之外,你還可以使用 anchor-size() 函式在尺寸屬性值中,相對於錨點的大小來調整元素的大小。

可以接受 anchor-size() 值的尺寸屬性包括:

anchor-size() 函式解析為 <length> 值。它們的語法如下:

anchor-size(<anchor-name> <anchor-size>, <length-percentage>)
<anchor-name>

設定為你想要調整元素大小所依據的錨點元素的 anchor-name 屬性值的 <dashed-ident> 名稱。如果省略,將使用元素的預設錨點,即在 position-anchor 屬性中引用的錨點。

<anchor-size>

指定定位元素將要參照的錨點元素的尺寸。這可以使用物理值(widthheight)或邏輯值(inlineblockself-inlineself-block)來表示。

<length-percentage>

指定在元素不是絕對定位或固定定位,或者錨點元素不存在時用作回退值的尺寸。

你將使用的最常見的 anchor-size() 函式只會引用預設錨點的某個維度。你也可以在 calc() 函式中使用它們,以修改應用於定位元素的尺寸。

例如,此規則將定位元素的寬度設定為等於預設錨點元素的寬度:

css
.elem {
  width: anchor-size(width);
}

這條規則將定位元素的內聯尺寸設定為錨點元素內聯尺寸的 4 倍,乘法是在 calc() 函式內部完成的:

css
.elem {
  inline-size: calc(anchor-size(self-inline) * 4);
}

讓我們看一個例子。HTML 和基礎 CSS 與前面的例子相同,只是錨點元素被賦予了 tabindex="0" 屬性以使其可聚焦。資訊框被賦予固定定位,並以與之前相同的方式與錨點關聯。然而,這一次我們使用 position-area 將其繫結到錨點的右側,並使其寬度為錨點寬度的五倍:

css
.infobox {
  position: fixed;
  position-anchor: --my-anchor;
  position-area: right;
  margin-left: 5px;
  width: calc(anchor-size(width) * 5);
}

此外,我們在 :hover:focus 狀態下增加了錨點元素的 width,併為其添加了 transition,以便在狀態變化時有動畫效果。

css
.anchor {
  text-align: center;
  width: 30px;
  transition: 1s width;
}

.anchor:hover,
.anchor:focus {
  width: 50px;
}

將滑鼠懸停在錨點元素上或用 Tab 鍵聚焦到它——定位元素會隨著錨點的增長而增長,這表明錨點定位元素的尺寸是相對於其錨點的:

anchor-size() 的其他用法

你也可以在物理和邏輯的內邊距和外邊距屬性中使用 anchor-size()。下面的部分將更詳細地探討這些用法,然後提供一個使用示例。

基於錨點尺寸設定元素位置

你可以在內邊距屬性值中使用 anchor-size() 函式來根據其錨點元素的尺寸定位元素,例如:

css
left: anchor-size(width);
inset-inline-end: anchor-size(--my-anchor height, 100px);

這並不會像 anchor() 函式或 position-area 屬性那樣,根據錨點的位置來定位一個元素(參見上面的相對於錨點定位元素);當錨點移動時,元素的位置不會改變。相反,元素將根據 absolutefixed 定位的正常規則進行定位。

這在某些情況下可能很有用。例如,如果你的錨點元素只能垂直移動,並且總是水平地保持在其最近的已定位祖先的邊緣旁邊,你可以使用 left: anchor-size(width) 來使錨點定位元素總是位於其錨點的右側,即使錨點寬度發生變化。

基於錨點尺寸設定元素外邊距

你可以在 margin-* 屬性值中使用 anchor-size() 函式,根據其錨點元素的尺寸來設定元素的外邊距,例如:

css
margin-left: calc(anchor-size(width) / 4);
margin-block-start: anchor-size(--my-anchor self-block, 20px);

這在某些情況下很有用,例如當你希望錨點定位元素的外邊距始終等於錨點元素寬度的相同百分比時,即使寬度發生變化。

anchor-size() 位置和外邊距示例

讓我們看一個例子,其中我們根據錨點元素的寬度來設定錨點定位元素的外邊距和位置。

在 HTML 中,我們指定了兩個 <div> 元素,一個 anchor 元素和一個 infobox 元素,我們將相對於錨點來定位它。我們給錨點元素一個 tabindex 屬性,以便可以透過鍵盤聚焦。我們還包含了一些填充文字,使 <body> 足夠高以需要滾動,但為了簡潔起見,這部分內容已被隱藏。

html
<div class="anchor" tabindex="0">⚓︎</div>

<div class="infobox">
  <p>Infobox.</p>
</div>

在 CSS 中,我們首先透過給 anchor <div> 一個 anchor-name 來將其宣告為錨點元素。定位元素的 position 屬性設定為 absolute,並透過其 position-anchor 屬性與錨點元素關聯。我們還在錨點和資訊框上設定了絕對的 heightwidth 尺寸,並在錨點上包含了一個 transition,以便在狀態改變時寬度變化能夠平滑地動畫化:

css
.anchor {
  anchor-name: --my-anchor;
  width: 100px;
  height: 100px;
  transition: 1s all;
}

.infobox {
  position-anchor: --my-anchor;
  position: absolute;
  height: 100px;
  width: 100px;
}

現在來看最有趣的部分。在這裡,我們設定了當錨點被懸停或聚焦時,其 width300px。然後我們設定了資訊框的:

  • top 值為 anchor(top)。這使得資訊框的頂部始終與錨點的頂部保持在一條線上。
  • left 值為 anchor-size(width)。這使得資訊框的左側距離其最近的已定位祖先的左邊緣有指定的距離。在這種情況下,指定的距離等於錨點元素的寬度,而最近的已定位祖先是 <body> 元素,所以資訊框出現在錨點的右側。
  • margin-left 值為 calc(anchor-size(width)/4)。這使得資訊框總有一個左外邊距,將其與錨點分開,該外邊距等於錨點寬度的四分之一。
css
.anchor:hover,
.anchor:focus {
  width: 300px;
}

.infobox {
  top: anchor(top);
  left: anchor-size(width);
  margin-left: calc(anchor-size(width) / 4);
}

渲染結果如下:

嘗試用 Tab 鍵聚焦到錨點或用滑鼠指標懸停在它上面,注意資訊框的位置和左外邊距是如何隨著錨點元素寬度的增加而成比例增長的。

另見