透過 CSS 錨定位置互相共用元素

您目前如何連結元素?您可以嘗試追蹤廣告的位置,或使用某種形式的包裝函式元素。

<!-- 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 中試用「實驗性 Web Platform 功能」的 CSS Anchoring API旗標。如要啟用該標記,請開啟 Chrome Canary 並前往 chrome://flags。然後啟用「實驗性網頁平台功能」旗標。

Oddbird 團隊也提供開發中的 polyfill。請務必前往 github.com/oddbird/css-anchor-positioning 查看存放區。

您可以使用以下功能檢查錨定支援功能:

@supports(anchor-name: --foo) {
  /* Styles... */
}

請注意,這個 API 目前仍在實驗階段,可能會有所變動。本文將概略介紹這些重要部分。此外,目前的導入方式也並未與 CSS 工作群組規格完全同步。

問題

為什麼要這麼做?其中一種顯著的應用實例是建立工具提示或類似工具提示的體驗。在這種情況下,建議您為工具提示參照的內容建立網路共用功能。您通常需要用某種方法將元素與另一個元素共用。您也會預期與頁面互動並不會對共用網路造成破壞,例如當使用者捲動畫面或調整使用者介面大小時。

另一個問題是,如何確保網路共用元素持續顯示在畫面上,例如當您開啟工具提示,導致圖片遭到可視區域邊界裁剪時。這可能無法為使用者提供良好的體驗。你想讓工具提示進行調整。

目前的解決方案

目前您可以透過幾種方式解決這個問題。

第一個是基本「包裝錨點」。您要將兩個元素納入容器中,接著,您就能使用 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 屬性定義。
  • 錨定側:所要使用位置的關鍵字。可以是 toprightbottomleftcenter 等等,也可以傳遞百分比。例如,50% 就是 center
  • 備用值:這是選填的備用值,可接受長度或百分比。

您可以使用 anchor 函式做為錨定元素的插邊屬性 (toprightbottomleft 或其邏輯的同等元素) 的值。您也可以在 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 的值,免費取得翻轉。舉例來說,如果您針對 bottom 使用 anchor,請將 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 的內容選單。概念是在點選 V 形標記按鈕後,顯示內容選單。而且選單具有專屬的展開選單

標記並不是重要的部分。不過,每個使用 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。請嘗試透過選取項目變更內容位置。您選擇的選項會更新格線對齊方式。這會影響錨定廣告的位置。

聚焦與追蹤

此示範影片結合了 CSS 基本功能,加入了 :has()。概念是為已聚焦的 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;
}

但該如何進一步發展呢?您可以用它做為某種形式的教學疊加層。工具提示可在搜尋點之間移動並更新內容。您可以將內容交叉淡出。獨立的動畫可讓你displayView Transitions 建立動畫。

長條圖計算

錨定位置的另一個有趣功能,就是結合 calc。假設在圖表中,您加入了部分彈出式視窗為圖表加上註解,

您可以使用 CSS minmax 追蹤最高和最低的值。的 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 順利運作?我們會將此名稱儲存在下一篇文章中。請放心,我們即將推出可設定樣式的精選元素。敬請持續鎖定!


就是這麼簡單!

網路平台不斷演進,CSS 錨定位置是改善 UI 控制項開發方式的重要關鍵。這樣一來,您就不會錯過某些棘手的決定。此外,這項工具也能讓您執行前所未有的功能。例如設定 <select> 元素的樣式!請提供您寶貴的意見。

相片來源:CHUTTERSNAP,來源為 Unsplash