您目前如何將一個元素連結至另一個元素?您可以嘗試追蹤其位置,或使用某種形式的包裝函式元素。
<!-- index.html -->
<div class="container">
<a href="/link" class="anchor">I’m the anchor</a>
<div class="anchored">I’m the anchored thing</div>
</div>
/* styles.css */
.container {
position: relative;
}
.anchored {
position: absolute;
}
這些解決方案通常不理想。需要 JavaScript 或額外的標記。CSS 錨點定位 API 旨在解決這個問題,提供可連結元素的 CSS API。可讓您根據其他元素的位置和大小,設定一個元素的位置和大小。
瀏覽器支援
您可以在 Chrome Canary 中試用 CSS 錨點定位 API,這項功能位於「Experimental Web Platform Features」標記後方。如要啟用該標記,請開啟 Chrome Canary 並前往 chrome://flags
。然後啟用「實驗性網站平台功能」旗標。
Oddbird 團隊也正在開發polyfill。請務必前往 github.com/oddbird/css-anchor-positioning 查看存放區。
您可以使用下列方法檢查是否支援錨點:
@supports(anchor-name: --foo) {
/* Styles... */
}
請注意,此 API 仍處於實驗階段,可能會有所變動。本文將概略說明重要的部分。目前的實作方式也未完全與 CSS 工作群組規格同步。
問題
為什麼需要這麼做?其中一個常見用途,就是建立工具提示或類似工具提示的體驗。在這種情況下,您通常會將工具提示連結至參照的內容。您通常需要某種方式將元素連結至另一個元素。您也希望與網頁互動時不會中斷連結,例如使用者捲動或調整 UI 大小。
另一個問題是,如果您想確保繫結元素會保留在檢視畫面中,例如您開啟工具提示,但它會遭到檢視區範圍裁剪。這可能會讓使用者體驗不佳。您希望工具提示能隨之調整。
目前的解決方案
目前有幾種方法可解決這個問題。
首先,我們要介紹最基本的「包裝錨點」方法。您可以將這兩個元素取出,並將它們包裝在容器中。接著,您可以使用 position
將工具提示相對於錨點定位。
<div class="containing-block">
<div class="tooltip">Anchor me!</div>
<a class="anchor">The anchor</a>
</div>
.containing-block {
position: relative;
}
.tooltip {
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%);
}
您可以移動容器,大部分情況下,所有內容都會保留在您想要的位置。
如果您知道錨點的位置,或可以以某種方式追蹤錨點,則可以採用其他方法。您可以將其傳遞至自訂屬性的工具提示。
<div class="tooltip">Anchor me!</div>
<a class="anchor">The anchor</a>
:root {
--anchor-width: 120px;
--anchor-top: 40vh;
--anchor-left: 20vmin;
}
.anchor {
position: absolute;
top: var(--anchor-top);
left: var(--anchor-left);
width: var(--anchor-width);
}
.tooltip {
position: absolute;
top: calc(var(--anchor-top));
left: calc((var(--anchor-width) * 0.5) + var(--anchor-left));
transform: translate(-50%, calc(-100% - 10px));
}
但如果不知道錨點的位置怎麼辦?您可能需要透過 JavaScript 介入。您可以執行類似以下程式碼的操作,但這表示樣式開始從 CSS 流出,並進入 JavaScript。
const setAnchorPosition = (anchored, anchor) => {
const bounds = anchor.getBoundingClientRect().toJSON();
for (const [key, value] of Object.entries(bounds)) {
anchored.style.setProperty(`--${key}`, value);
}
};
const update = () => {
setAnchorPosition(
document.querySelector('.tooltip'),
document.querySelector('.anchor')
);
};
window.addEventListener('resize', update);
document.addEventListener('DOMContentLoaded', update);
這會引發一些問題:
- 何時計算樣式?
- 如何計算樣式?
- 我要多久計算一次樣式?
這樣解決問題了嗎?這可能適用於您的用途,但有一個問題:我們的解決方案無法調整。沒有回應。如果錨定元素遭到可視區域截斷,該怎麼辦?
接下來,您需要決定是否要回應,以及如何回應。您需要回答的問題和做出的決策數量開始增加。您只需要將一個元素連結至另一個元素即可。在理想情況下,您的解決方案會根據周遭環境調整及做出反應。
為了減輕這方面的痛苦,您可以使用 JavaScript 解決方案來解決問題。這會導致在專案中加入依附元件的成本,而且可能會因使用方式而導致效能問題。舉例來說,某些套件會使用 requestAnimationFrame
來維持正確的位置。也就是說,您和您的團隊必須熟悉套件及其設定選項。因此,您的問題和決策可能不會減少,但會有所變更。這就是 CSS 錨點定位的「原因」之一。這樣一來,您在計算位置時,就不會需要考慮效能問題。
以下是使用「floating-ui」這個常用套件 (用於解決這個問題) 的程式碼:
import {computePosition, flip, offset, autoUpdate} from 'https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.2.1/+esm';
const anchor = document.querySelector('.anchor')
const tooltip = document.querySelector('.tooltip')
const updatePosition = () => {
computePosition(anchor, tooltip, {
placement: 'top',
middleware: [offset(10), flip()]
})
.then(({x, y}) => {
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`
})
})
};
const clean = autoUpdate(anchor, tooltip, updatePosition);
請嘗試在使用該程式碼的這個示範中重新調整錨點位置。
「工具提示」的運作方式可能不如預期。它會對 y 軸上的可視區域外部做出反應,但不會對 x 軸做出反應。深入研究說明文件,很可能就能找到適合自己的解決方案。
不過,尋找適合專案的套件可能會耗費大量時間。這需要額外決定,而且如果無法按照預期運作,可能會讓您感到挫折。
使用錨定定位
輸入 CSS 錨點定位 API。這個概念是將樣式保留在 CSS 中,並減少需要做出的決策次數。您希望達到相同的結果,但目標是改善開發人員體驗。
- 不需要 JavaScript。
- 讓瀏覽器根據您的指示找出最佳位置。
- 不再有第三方依附元件
- 沒有包裝元素。
- 適用於頂層元素。
讓我們重新建立上述問題,並嘗試解決。但請改用船隻與錨的類比。這些元素分別代表錨定元素和錨點。水代表包含區塊。
首先,您需要選擇如何定義錨點。您可以在 CSS 中設定錨點元素的 anchor-name
屬性,即可執行這項操作。可接受 破折線標示的識別項值。
.anchor {
anchor-name: --my-anchor;
}
或者,您也可以使用 anchor
屬性在 HTML 中定義錨點。屬性值是錨點元素的 ID。這會建立隱含錨點。
<a id="my-anchor" class="anchor"></a>
<div anchor="my-anchor" class="boat">I’m a boat!</div>
定義錨點後,您可以使用 anchor
函式。anchor
函式採用 3 個引數:
- 錨點元素:要使用的錨點的
anchor-name
,或者您可以省略值,使用implicit
錨點。您可以透過 HTML 關係定義,也可以使用具有anchor-name
值的anchor-default
屬性。 - 錨點側:您要使用的錨點位置關鍵字。這可以是
top
、right
、bottom
、left
、center
等,也可以傳遞百分比。例如,50% 等於center
。 - 備用值:這是可選的備用值,可接受長度或百分比。
您可以使用 anchor
函式做為錨定元素的內嵌屬性 (top
、right
、bottom
、left
或其邏輯等效值) 的值。您也可以在 calc
中使用 anchor
函式:
.boat {
bottom: anchor(--my-anchor top);
left: calc(anchor(--my-anchor center) - (var(--boat-size) * 0.5));
}
/* alternative with anchor-default */
.boat {
anchor-default: --my-anchor;
bottom: anchor(top);
left: calc(anchor(center) - (var(--boat-size) * 0.5));
}
由於沒有 center
內嵌屬性,因此如果您知道錨定元素的大小,可以使用 calc
。為何不使用 translate
?您可以使用以下做法:
.boat {
anchor-default: --my-anchor;
bottom: anchor(top);
left: anchor(center);
translate: -50% 0;
}
不過,瀏覽器不會考量錨定元素的轉換位置。在考量位置備用和自動定位時,您就會明白為何這麼做很重要。
您可能注意到上述使用了自訂屬性 --boat-size
。不過,如果您想讓錨點元素的大小取決於錨點的大小,也可以存取該大小。您可以使用 anchor-size
函式,而非自行計算。舉例來說,假設船隻的寬度是錨的四倍:
.boat {
width: calc(4 * anchor-size(--my-anchor width));
}
您也可以使用 anchor-size(--my-anchor height)
存取高度。您可以使用它來設定任一軸的大小,也可以設定兩者的大小。
如果您想將錨點設為 absolute
定位元素,規則是元素不得為同層元素。在這種情況下,您可以使用具有 relative
定位功能的容器包裝錨點。接著,您就可以將錨點設為該圖片。
<div class="anchor-wrapper">
<a id="my-anchor" class="anchor"></a>
</div>
<div class="boat">I’m a boat!</div>
請查看這部示範影片,您可以拖曳錨點,船隻就會跟隨。
追蹤捲動位置
在某些情況下,錨點元素可能位於捲動容器內。同時,錨定元素可能位於該容器之外。由於捲動動作會在版面配置的不同執行緒上發生,因此您需要一種追蹤方式。anchor-scroll
屬性可以執行這項操作。您可以在錨定元素上設定這個屬性,並為其指定要追蹤的錨點值。
.boat { anchor-scroll: --my-anchor; }
請試試這個示範,您可以使用角落中的核取方塊,開啟或關閉 anchor-scroll
。
不過,這項比喻在本例中並不完全適用,因為在理想情況下,您的船和錨都會在水中。此外,Popover API 等功能可讓您保持相關元素的緊密連結。不過,錨點定位可用於頂層的元素。這是 API 的其中一個主要優點:能夠將不同流程中的元素綁定在一起。
請參考這個示範,其中包含捲動容器,以及含有工具提示的錨點。彈出式工具提示元素可能不會與錨點一併顯示:
但您會發現彈出式視窗會追蹤各自的錨點連結。您可以調整捲動容器的大小,系統會自動更新位置。
備用廣告排序和自動廣告排序
這就是錨點位置的力量提升的關鍵。position-fallback
可根據您提供的一組備用項目,為錨定元素定位。您可以透過樣式引導瀏覽器,讓瀏覽器為您找出位置。
這裡的常見用途是,在錨點上方或下方顯示工具提示。這項行為取決於工具提示是否會遭到容器裁剪。這個容器通常是可視區域。
如果您深入研究上一個示範的程式碼,就會發現其中使用了 position-fallback
屬性。如果您捲動容器,可能會發現這些錨定的彈出式視窗會跳動。當各自的錨點靠近檢視區邊界時,就會發生這種情況。此時,彈出式視窗會嘗試調整,以便停留在檢視區中。
在建立明確的 position-fallback
之前,錨點定位功能也會提供自動定位功能。您可以在錨點函式和相反的內嵌屬性中使用 auto
值,免費取得翻轉效果。舉例來說,如果您將 anchor
用於 bottom
,請將 top
設為 auto
。
.tooltip {
position: absolute;
bottom: anchor(--my-anchor auto);
top: auto;
}
自動定位的替代做法是使用明確的 position-fallback
。您必須定義位置備用集合。瀏覽器會逐一檢查這些位置,直到找到可用的一個位置為止,然後套用該位置。如果找不到可用的鍵盤,系統會預設使用定義的第一個鍵盤。
嘗試在上方和下方顯示工具提示的 position-fallback
可能會如下所示:
@position-fallback --top-to-bottom {
@try {
bottom: anchor(top);
left: anchor(center);
}
@try {
top: anchor(bottom);
left: anchor(center);
}
}
將這項屬性套用至工具提示的效果如下:
.tooltip {
anchor-default: --my-anchor;
position-fallback: --top-to-bottom;
}
使用 anchor-default
表示您可以將 position-fallback
重複用於其他元素。您也可以使用範圍限定的自訂屬性來設定 anchor-default
。
請再看看這部使用船隻的示範影片。有 position-fallback
組合。當您變更錨點的位置時,船隻會調整位置,以便停留在容器內。請嘗試變更邊框間距值,以調整主體邊框間距。請注意瀏覽器如何修正位置。位置會隨著容器的格狀對齊方式而變更。
這次 position-fallback
會以順時針方向嘗試位置,因此會比較冗長。
.boat {
anchor-default: --my-anchor;
position-fallback: --compass;
}
@position-fallback --compass {
@try {
bottom: anchor(top);
right: anchor(left);
}
@try {
bottom: anchor(top);
left: anchor(right);
}
@try {
top: anchor(bottom);
right: anchor(left);
}
@try {
top: anchor(bottom);
left: anchor(right);
}
}
範例
瞭解錨點定位的主要功能後,我們來看看除了工具提示之外的其他實用範例。這些範例旨在激發你的靈感,讓你瞭解如何使用錨點定位。要進一步改善規格,最理想的方式就是收集真實使用者的意見回饋。
內容選單
讓我們從使用 Popover API 的內容功能表開始。點選帶有箭頭的按鈕時,會顯示內容選單。而該選單會提供可展開的選單。
標記並非這裡的重點。但您有三個按鈕都使用 popovertarget
。接著,您有三個使用 popover
屬性的元素。這樣一來,您就能在不使用任何 JavaScript 的情況下開啟內容選單。如下所示:
<button popovertarget="context">
Toggle Menu
</button>
<div popover="auto" id="context">
<ul>
<li><button>Save to your Liked Songs</button></li>
<li>
<button popovertarget="playlist">
Add to Playlist
</button>
</li>
<li>
<button popovertarget="share">
Share
</button>
</li>
</ul>
</div>
<div popover="auto" id="share">...</div>
<div popover="auto" id="playlist">...</div>
您現在可以定義 position-fallback
,並在內容選單之間共用。我們也確保為彈出式視窗取消設定任何 inset
樣式。
[popovertarget="share"] {
anchor-name: --share;
}
[popovertarget="playlist"] {
anchor-name: --playlist;
}
[popovertarget="context"] {
anchor-name: --context;
}
#share {
anchor-default: --share;
position-fallback: --aligned;
}
#playlist {
anchor-default: --playlist;
position-fallback: --aligned;
}
#context {
anchor-default: --context;
position-fallback: --flip;
}
@position-fallback --aligned {
@try {
top: anchor(top);
left: anchor(right);
}
@try {
top: anchor(bottom);
left: anchor(right);
}
@try {
top: anchor(top);
right: anchor(left);
}
@try {
bottom: anchor(bottom);
left: anchor(right);
}
@try {
right: anchor(left);
bottom: anchor(bottom);
}
}
@position-fallback --flip {
@try {
bottom: anchor(top);
left: anchor(left);
}
@try {
right: anchor(right);
bottom: anchor(top);
}
@try {
top: anchor(bottom);
left: anchor(left);
}
@try {
top: anchor(bottom);
right: anchor(right);
}
}
這樣一來,您就能取得自適應巢狀內容選單 UI。請嘗試使用選取器變更內容位置。您選擇的選項會更新格線對齊方式。這會影響錨點定位方式,進而影響彈出式視窗的位置。
聚焦和追蹤
這個示範會透過引入 :has() 結合 CSS 基本元素。其概念是針對有焦點的 input
轉換視覺指標。
方法是在執行階段設定新的錨點。在這個示範中,系統會在輸入焦點時更新受限自訂屬性。
#email {
anchor-name: --email;
}
#name {
anchor-name: --name;
}
#password {
anchor-name: --password;
}
:root:has(#email:focus) {
--active-anchor: --email;
}
:root:has(#name:focus) {
--active-anchor: --name;
}
:root:has(#password:focus) {
--active-anchor: --password;
}
:root {
--active-anchor: --name;
--active-left: anchor(var(--active-anchor) right);
--active-top: calc(
anchor(var(--active-anchor) top) +
(
(
anchor(var(--active-anchor) bottom) -
anchor(var(--active-anchor) top)
) * 0.5
)
);
}
.form-indicator {
left: var(--active-left);
top: var(--active-top);
transition: all 0.2s;
}
但您可以如何進一步提升?您可以將其用於某些形式的教學疊加層。工具提示可在各個興趣點之間移動,並更新內容。您可以使用交疊淡出/淡入效果。您可以使用可為 display
或View 轉場製作動畫的獨立動畫。
長條圖計算
錨點定位的另一個有趣用途,就是與 calc
結合使用。假設您在圖表中使用了一些彈出式視窗來註解圖表。
您可以使用 CSS min
和 max
追蹤最高和最低值。相關的 CSS 可能會如下所示:
.chart__tooltip--max {
left: anchor(--chart right);
bottom: max(
anchor(--anchor-1 top),
anchor(--anchor-2 top),
anchor(--anchor-3 top)
);
translate: 0 50%;
}
系統會使用部分 JavaScript 更新圖表值,並使用部分 CSS 設定圖表樣式。不過,錨點位置會為我們處理版面配置更新。
大小調整控點
您不必只錨定至一個元素。您可以為元素使用多個錨點。您可能已經在長條圖範例中注意到這點。工具提示會錨定在圖表和相應的長條圖上。如果您進一步瞭解這個概念,就可以用來調整元素大小。
您可以將定位點視為自訂調整大小的控制點,並傾向使用 inset
值。
.container {
position: absolute;
inset:
anchor(--handle-1 top)
anchor(--handle-2 right)
anchor(--handle-2 bottom)
anchor(--handle-1 left);
}
在這個示範中,GreenSock Draggable 會讓手把具備可拖曳功能。不過,<img>
元素會調整大小,以填滿容器,並調整填滿手柄之間的空白。
選取選單?
最後一項功能是對未來的預告。不過,您可以建立可聚焦的彈出式視窗,現在您已可使用錨點定位。您可以建立可設定樣式的 <select>
元素基礎。
<div class="select-menu">
<button popovertarget="listbox">
Select option
<svg>...</svg>
</button>
<div popover="auto" id="listbox">
<option>A</option>
<option>Styled</option>
<option>Select</option>
</div>
</div>
使用隱含的 anchor
可簡化這項作業。不過,初步的 CSS 可能會像這樣:
[popovertarget] {
anchor-name: --select-button;
}
[popover] {
anchor-default: --select-button;
top: anchor(bottom);
width: anchor-size(width);
left: anchor(left);
}
將 Popover API 的功能與 CSS 錨點定位功能結合,就能達到這個效果。
您可以開始引入 :has()
之類的東西,您可以在開啟時旋轉標記:
.select-menu:has(:open) svg {
rotate: 180deg;
}
接下來要去哪裡?我們還需要做什麼才能讓 select
正常運作?我們會在下一篇文章中介紹這項功能。不過別擔心,我們會推出可設定樣式的 Select 元素。敬請持續鎖定!
就是這麼簡單!
網路平台不斷進化,CSS 錨點定位是改善 UI 控制項開發方式的重要一環。讓您不必做出一些棘手的決定。但也能讓你執行先前無法執行的操作。例如設定 <select>
元素的樣式!請提供您寶貴的意見。
相片來源:CHUTTERSNAP 發布於 Unsplash 網站