懶人包
被影片 Make this fun effect that follows your cursor (pure CSS) 啟發,發現 CSS anchor position 的 anchor() 也能用來實現 Tab 元件的標籤左右滑動動畫效果,而且要寫的樣式行數少很多,讀起來也沒有比較複雜。
最大的缺點就是這篇筆記發佈時,火狐和 safari 都還不支援 anchor() 🌚

如何做出 Tab 滑動效果
傳統做法
傳統上,當前端要在 Tab 元件搞出「根據點擊目標,表現出標籤水平滑動效果」時,工程師都要根據容器寬算一些數學。以「佔滿容器寬,並且有三個標籤的 Tab 元件」為例:
<form class="traditional"> <fieldset> <label> <input type="radio" name="fruit" value="apple" checked /> apple </label> <label> <input type="radio" name="fruit" value="banana" /> banana </label> <label> <input type="radio" name="fruit" value="cherry" /> cherry </label> </fieldset></form>.traditional fieldset { position: relative; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); --gap: 8px; gap: var(--gap); margin: 0; --padding: 4px; padding: var(--padding); isolation: isolate;}
.traditional fieldset::after { content: ""; position: absolute; inset: var(--padding); background-color: salmon; width: calc((100% / 3) - var(--gap)); border-radius: 4px; z-index: -10; transform: translatex(var(--trans-x)); transition-property: transform; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms;}
.traditional fieldset:has(label:nth-of-type(1) input:checked) { --trans-x: 0;}
.traditional fieldset:has(label:nth-of-type(2) input:checked) { --trans-x: calc(100% + var(--gap));}
.traditional fieldset:has(label:nth-of-type(3) input:checked) { --trans-x: calc(200% + (var(--gap) * 2));}首先把 .traditional fieldset::after 設定為標籤。因為整個元件總共有三個標籤,所以一個標籤的寬度是「總容器寬除以三之後,再扣掉 gap 的寬」。
接著,根據使用者當下點選了哪一個 input:radio 來更新 --trans-x 的值。特別注意 --trans-x 是設定在 .traditional fieldset 身上。如果把選取器寫成 .traditional fieldset label:nth-of-type(1) input:checked 是無法作用的——因為 CSS 變數只能從親代繼承下來,不用 :has() 來選取 .traditional fieldset 會讓 --trans-x 作用於 input:checked,那 .traditional fieldset::after 就吃不到更新後的 --trans-x 了。
以 anchor() 實現
html 結構與上一節相同,但改用 anchor-name 搭配 anchor() 來指定標籤的停留位置。簡單來說:
- 先透過
.anchor label:has(input:checked)來把anchor-name: --a指定到「被使用者點選的input身上」 - 然後指定
.anchor fieldset::after以position-anchor: --a來計算top/right/bottom/left - 當使用者點擊任一選項時,標籤就會滑動到對應的
input區塊
.anchor fieldset { position: relative; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin: 0; padding: 4px; isolation: isolate;}
.anchor label:has(input:checked) { anchor-name: --a;}
.anchor fieldset::after { content: ""; /* 重點開始 */ position: absolute; position-anchor: --a; top: anchor(top); right: anchor(right); bottom: anchor(bottom); left: anchor(left); /* 重點結束 */ background-color: salmon; border-radius: 4px; z-index: -10; transition-property: all; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms;}改用 anchor() 後,就不用去計算標籤的寬度了 🥳
完整程式碼
See the Pen tab switch by Charlie (@Charlie7779) on CodePen.
參考資料
- CSS anchor()
- Anchoreum: 對 anchor position 完全沒有概念的讀者,可以先從這個互動式學習站開始嘗試看看