現代 CSS 的新力量

![](https://i.imgur.com/2sfAIkh.jpeg)

如果你拆開一個稍微複雜一點的 Web 應用,很容易發現一個熟悉的現象。在還沒寫任何業務程式碼之前,就已經引入了一堆 JavaScript 套件,只是為了實作一些基礎的 UI 行為,比方說提示框定位、捲動動畫、彈出視窗管理,甚至一個簡單的下拉選擇框。而現在,這種情況正在改變——越來越多這類能力,開始被 CSS 接管。

以提示框(Tooltip)和彈出視窗(Popover)為例,過去通常需要依賴像 Floating UI Popper 這樣的函式庫來計算位置、處理溢位、實作自動翻轉。這類問題本質上是「版面配置問題」,卻一直由 JavaScript 來解決。如今,CSS 錨點定位以及原生 Popover API 已經可以直接完成這些工作,瀏覽器會自動處理邊界與對齊,不再需要手寫複雜的邏輯。

類似的變化也發生在捲動動畫上。過去若想讓動畫跟隨捲動進度變化,幾乎離不開 GSAP ScrollTrigger。Web 開發者需要監聽 scroll 事件、計算位置,再把結果映射到動畫上。而現在,CSS 提供了捲動驅動動畫(如 scroll-timeline),可以用宣告式的方式直接把捲動和動畫綁在一起,不僅程式碼更簡潔,而且效能更好,因為這些計算是在瀏覽器內部完成的。

在交互動效方面也是如此。像 Framer Motion 這類函式庫長期以來負責處理狀態切換、進入與離開動畫,甚至頁面過渡。但隨著 CSS 動畫能力的增強(例如更強的 transition、@starting-style 以及視圖過渡),這些原本依賴 JavaScript 控制時間軸的效果,正在逐漸轉向用 CSS 描述。

再看彈出視窗與元件行為。過去我們常借助 Radix UI Headless UI 來處理模態框的焦點管理、鍵盤交互和可存取性細節。這些不只是「顯示一個彈出視窗」,而是涉及一整套複雜行為。而現在,瀏覽器提供了 <dialog></dialog> 和 popover 等原生能力,很多行為已經內建,不再需要額外的套件來兜底。

最後是表單控件這個長期的痛點。由於原生 <select></select> 難以樣式化,開發者往往選擇使用諸如 React Select 這樣的套件,從零實作一個元件。但隨著 CSS 對原生控件樣式能力的不斷增強,我們越來越可以在保留原生語意和可存取性的前提下,對其進行自訂,而不是徹底重寫。

把這些變化放在一起看,可以發現一個清晰的趨勢:我們正在從「用 JavaScript 補足瀏覽器能力」,轉向「直接使用瀏覽器提供的能力」。這不僅代表更少的程式碼體積,也代表更好的效能、更低的維護成本,以及更少需要手動處理的邊界問題。本質上,這是一種回歸——讓 CSS 負責它本該負責的事情。

換句話說,一批被期待已久的 CSS 特性正在陸續落地,而且它們的目標很明確:替代那些過去必須依賴 JavaScript 才能實現的 UI 模式。而且,這不是「並不怎麼能用」的替代方案。它們是瀏覽器提供的平台級能力——能夠處理邊界情形、運行在正確的合成執行緒上,並且不依賴任何第三方函式庫。

接下來,我們一起來看看,這些能力已經發展到了什麼程度,它們具體替代了哪些 JavaScript 方案,你可以刪掉多少程式碼,以及還有哪些問題,CSS 仍然沒解決。

錨點定位

在很長一段時間裡,從提示框(Tooltip)、下拉選單(DropdownMenu)、彈出視窗(Popover)到各種浮動選單,這類 Web UI 一直依賴 JavaScript 來「貼住」觸發元素。你要麼使用第三方 JavaScript 函式庫,要麼自己寫一套基於 getBoundingClientRect 的計算邏輯,在捲動、縮放和版面變動時不斷更新位置。歸根結底,瀏覽器本身一直缺少一個原生能力——讓一個元素跟隨另一個元素定位。

CSS 錨點定位從根本上改變了這一點。現在,你可以用 anchor-name 宣告一個錨點元素,然後透過 position-anchor 將目標元素與錨點元素關聯起來,再透過 anchor() 函數或者 position-area 與 position-try,將目標元素相對於錨點元素進行定位。所有的位置計算都交給瀏覽器完成,而且不僅僅是「放在那裡」,連溢位處理也一併解決。例如,當 Tooltip 可能被視窗裁切時,你可以定義多個備用位置,瀏覽器會按順序自動嘗試並選擇合適的方案。

<div><div><div></div><span>CSS</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>.button</span> {</span>
<span>    anchor-name: --my-button;</span>
<span>}</span>
<span></span>
<span><span>.tooltip</span> {</span>
<span>    <span>position</span>: absolute;</span>
<span>    <span>position</span>-anchor: --my-button;</span>
<span>    <span>top</span>: <span>anchor</span>(bottom);</span>
<span>    <span>left</span>: <span>anchor</span>(left);</span>
<span>}</span>

![](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/1abf4d0fff50424fa75ce6ae767480cc~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5aSn5rygX3czY3BsdXNjb20=:q75.awebp?rk3s=f64ab15b&x-expires=1775995265&x-signature=Hi%2Bfs3rtc5Eaf521E%2Fbe9qN0saQ%3D)

> Demo 地址:[codepen.io/airen/full/…](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fairen%2Ffull%2FyyajVGb)

不需要 JavaScript、不需要 ResizeObserver,也不需要監聽 scroll。一旦建立了錨點關係,瀏覽器就會在捲動、尺寸變化以及版面變動時,自動保持浮層元素與錨點之間的對齊關係。這類原本需要頻繁計算和同步的邏輯,現在完全交由瀏覽器底層處理。

這裡展示的是基於 CSS 錨點定位實作的一個最基礎版本的提示框(Tooltip)元件。但它的能力遠不止於此。CSS 錨點定位不僅可以用來做提示框元件,還可以擴展到許多過去必須依賴 JavaScript 的互動場景,例如:熔岩導航選單、元素之間的聯動動畫、類似 macOS Dock 的導航效果、滑動跟隨的懸浮層、帶「磁性吸附」的懸浮互動,甚至是元素之間的連線效果等。這些原本需要複雜計算和事件監聽的功能,現在都可以用更原生、更簡潔的方式實作。

在瀏覽器支援方面,Google Chrome 已在 2024 年(Chrome 125)提供了較為完整的實作,而 Mozilla Firefox 和 Safari 也在持續推進中。目前,一些基礎能力(如 anchor-name、anchor() 函數和 position-area)已經可以在主流瀏覽器中使用;而像 position-try、position-visibility 這樣的進階特性,仍在逐步落地。

> 溫馨提示:如果你希望更系統、更深入地掌握 CSS 錨點定位相關特性,建議抽時間閱讀《[CSS 錨點定位: 探索下一代 Web 版面配置](https://juejin.cn/book/7223230325122400288/section/7259669151743279159)》和《[CSS 佈局:重聊 CSS 錨點定位](https://juejin.cn/book/7395119173656903715/section/7445249534449434674)》這兩節課程。它們會從基礎到進階,幫助你全面理解這一能力,並應用到實際專案中。

Popover API
-----------

![](https://i.imgur.com/bToB7vV.jpeg)

CSS 錨點定位和 HTML Popover API,其實是在解決同一個問題的兩個不同面向。CSS 錨點定位負責「浮層出現在哪裡」,HTML Popover API 負責「浮層是否出現」。也就是說,CSS 錨點定位處理位置,而 HTML Popover API 處理可見性、互動行為以及可存取性。一套完整的提示框(Tooltip)或彈出視窗(Popover)元件,通常需要兩者配合使用。

在 Popover API 出現之前,構建一個真正「可用」的 Popover、下拉選單或非模態彈框,往往需要使用 JavaScript 實作一整套複雜邏輯:焦點管理、維護 aria-expanded 狀態、處理鍵盤互動、監聽點擊外部關閉等。這些細節不僅繁瑣,而且非常容易出錯,這也是為什麼像 Headless UI、Radix UI 這類函式庫長期存在的原因。

而現在,事情變得簡單得多。透過 HTML 的 popover 屬性以及配套的 popovertarget,你只需要少量的 HTML 結構,就可以獲得一個原生、可存取的 Popover 元件。瀏覽器會自動幫你處理一系列關鍵行為,包括顯示與隱藏的切換、點擊外部關閉、按下 Escape 鍵關閉、使用 ::backdrop 建立背景遮罩、焦點管理,以及頂層渲染(popover 始終會顯示在頁面最上層,無需再處理複雜的 z-index)。

<div><div><div></div><span>HTML</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>button</span> <span>invoketarget</span>=<span>"ref_1"</span> <span>popovertarget</span>=<span>"ref_1"</span>></span>roll<span><span>button</span>></span>
<span><span>div</span> <span>popover</span> <span>id</span>=<span>"ref_1"</span> <span>class</span>=<span>"card"</span>></span><span><span>div</span>></span>

Demo 地址:codepen.io/airen/full/…

這些能力如今已在 Google Chrome、Mozilla Firefox 和 Safari 中成為基礎功能。對於絕大多數常見場景來說,它已經可以替代過去依賴各種第三方 JavaScript 套件所實作的整套互動邏輯。

Popover API 非常適合用來構建提示框、下拉選單、上下文選單、通知提示、引導提示等各種非模態浮層——也就是那些使用者點擊外部時應該自動關閉的介面。但需要注意的是,它並不是用來替代模態對話框的;對於需要阻止背景互動的場景,仍然應該使用 <dialog></dialog> 元素(稍後會詳細介紹)。

溫馨提示:如果你想更深入了解 Popover API 相關特性,請移步閱讀《CSS 佈局:CSS 與 Popover API 的結合》!

AIM(Anchor Interpolated Morph)

AIM(Anchor Interpolated Morph,錨點插值變形)是一種前衛的 Web 動畫技術,它的核心理念是將元素視為「錨點」,透過位置、尺寸和形狀的插值計算,實現元素之間的平滑過渡動畫。通俗來說,AIM 能讓一個元素看起來像是從另一個元素位置「生長」出來,或者在關閉時平滑地返回原來的位置。與傳統的過渡或動畫不同,AIM 不僅關注單一屬性的變化(例如位置或透明度),而是綜合考量元素的空間資訊與版面錨點,在不同狀態之間自動生成連貫的動畫路徑,讓介面變化更自然流暢,同時保留使用者的空間感知,讓使用者清楚理解元素之間的關係。從動畫效果來看,AIM 與早期的 F.L.I.P(First, Last, Invert 和 Play)技術非常相似。

只不過,在技術實作上,AIM 利用了一系列現代 CSS 特性。CSS 錨點定位(anchor() 和 anchor-size())允許一個元素將另一個元素的座標和尺寸作為起點,從而建立空間關聯。@starting-style 規則定義元素在渲染瞬間的樣式,例如彈出框初次出現的狀態,使入場動畫自然流暢。interpolate-size 屬性則支援在內聯關鍵字尺寸(如 auto 或 min-content)與具體長度尺寸(如 300px)之間動畫化,保證元素在狀態變化時平滑過渡。這些功能結合在一起,讓瀏覽器可以在渲染時自動計算元素的起始狀態和目標狀態,並在它們之間生成連續動畫,無需 JavaScript 干預,同時動畫可以自然中斷,回應使用者操作。

<div><div><div></div><span>HTML</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>div</span> <span>class</span>=<span>"cell"</span>></span>
<span>    <span>button</span> <span>popovertarget</span>=<span>"img-1"</span> <span>aria-label</span>=<span>"Open Image of Mountain Landscape"</span>></span>
<span>        <span>img</span> <span>src</span>=<span>"https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=700&h=400&fit=crop&auto=format"</span> <span>alt</span>=<span>"Mountain landscape"</span> <span>loading</span>=<span>"lazy"</span> /></span>
<span>    <span><span>button</span>></span></span>
<span>    <span>img</span> <span>popover</span> <span>id</span>=<span>"img-1"</span> <span>src</span>=<span>"https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1800&auto=format"</span> <span>alt</span>=<span>"Mountain landscape"</span> <span>loading</span>=<span>"lazy"</span> /></span>
<span><span><span>div</span>></span></span>

<div><div><div></div><span>CSS</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>:root</span> {</span>
<span> interpolate-size: allow-keywords;</span>
<span>}</span>
<span></span>
<span><span>button</span> {</span>
<span> anchor-name: --morph;</span>
<span>}</span>
<span></span>
<span><span>[popover]</span> {</span>
<span> <span>--speed</span>: <span>0.5s</span>;</span>
<span> <span>position</span>-anchor: --morph;</span>
<span> </span>
<span> span>@media</span (<span>prefers-reduced-motion</span>: no-preference) {</span>
<span> <span>transition</span>:</span>
<span> display <span>var</span>(--speed) allow-discrete,</span>
<span> overlay <span>var</span>(--speed) allow-discrete,</span>
<span> height <span>var</span>(--speed) ease,</span>
<span> width <span>var</span>(--speed) ease,</span>
<span> top <span>var</span>(--speed) ease,</span>
<span> left <span>var</span>(--speed) ease;</span>
<span> }</span>
<span> </span>
<span> &:popover-open {</span>
<span> <span>height</span>: auto; <span>/ 或 fit-content /</span></span>
<span> <span>max-height</span>: <span>70</span>dvb;</span>
<span> <span>width</span>: <span>70</span>dvi;</span>
<span> <span>left</span>: <span>15</span>dvi;</span>
<span> <span>top</span>: <span>15</span>dvb;</span>
<span> </span>
<span> span>@starting-style</span {</span>
<span> <span>left</span>: <span>anchor</span>(left);</span>
<span> <span>top</span>: <span>anchor</span>(top);</span>
<span> <span>width</span>: <span>anchor-size</span>(width);</span>
<span> <span>height</span>: <span>anchor-size</span>(height);</span>
<span> }</span>
<span> }</span>
<span> &<span>:not</span>(:popover-open) {</span>
<span> <span>left</span>: <span>anchor</span>(left);</span>
<span> <span>top</span>: <span>anchor</span>(top);</span>
<span> <span>width</span>: <span>anchor-size</span>(width);</span>
<span> <span>height</span>: <span>anchor-size</span>(height);</span>
<span> }</span>
<span>}</span>

AIM 技術在實際應用中非常適合圖片畫廊、列表展開和彈窗動畫等場景。例如,在圖片畫廊中,縮略圖可以作為錨點,大圖從縮略圖位置動畫展開,填滿螢幕;關閉時,大圖又平滑縮回原位。這種動畫方式不僅直觀、有趣,還增強了使用者對介面元素來源與去向的認知,使互動更易理解。

Demo 地址:codepen.io/airen/full/…

溫馨提示,如果你對 AIM 技術感興趣,建議花點時間閱讀《Web 動效:錨點定位之錨點插值變形(AIM)解析》和《Web 動效:用 AIM 做出高級感 UI 過渡》!

dialog 元素

如果說 Popover API 適用於非阻塞的浮層——使用者點擊外部即可關閉,那麼 HTML 的 <dialog></dialog> 則用於真正的模態體驗(模態框):在關閉之前,必須完整佔據使用者注意力的那類互動。

在傳統的 JavaScript 實作中,這類模態對話框往往需要一整套繁瑣的邏輯,例如建立背景遮罩(通常用 div 來建立)、模態框的定位、手動設定 aria-modal,在模態框內捕獲焦點、關閉時恢復焦點,以及禁止頁面滾動等。這些細節不僅複雜,而且很容易遺漏,進而影響可存取性。

而原生 <dialog></dialog> 元素把這一切都內建了。結合 ::backdrop 偽元素和 .showModal() 方法,你只需要一個 HTML 元素和少量的 JavaScript 就可以建立一個完整、可存取的模態對話框。瀏覽器會自動處理焦點管理、遮罩層以及互動行為,大大簡化了實作流程。

<div><div><div></div><span>HTML</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>button</span> <span>class</span>=<span>"button"</span> <span>onclick</span>=<span>"document.getElementById('my-dialog').showModal()"</span>></span>Open dialog<span><span>button</span>></span>
<span></span>
<span><span>dialog</span> <span>id</span>=<span>"my-dialog"</span>></span>
<span>    <span>div</span> <span>class</span>=<span>"dialog-header"</span>></span>
<span>        <span>div</span> <span>class</span>=<span>"dialog-title"</span>></span>Delete this file?<span><span>div</span>></span>
<span>    <span><span>div</span>></span></span>
<span>    <span>div</span> <span>class</span>=<span>"dialog-body"</span>></span>
<span>        This action cannot be undone. The file will be permanently removed from your workspace.</span>
<span>    <span><span>div</span>></span></span>
<span>    <span>div</span> <span>class</span>=<span>"dialog-footer"</span>></span>
<span>        <span>button</span> <span>class</span>=<span>"button secondary"</span> <span>onclick</span>=<span>"document.getElementById('my-dialog').close()"</span>></span>Cancel<span><span>button</span>></span>
<span>        <span>button</span> <span>class</span>=<span>"button"</span> <span>onclick</span>=<span>"document.getElementById('my-dialog').close()"</span>></span>Delete<span><span>button</span>></span>
<span>    <span><span>div</span>></span></span>
<span><span><span>dialog</span>></span></span>

其中最關鍵的「模態陷阱」——也就是 Tab 鍵只在對話框內部循環、按下 Escape 鍵自動關閉——同樣由瀏覽器原生處理。從 2022 年開始,這些能力已成為主流瀏覽器的基礎支援,讓模態對話框的實作從「複雜工程」變成「開箱即用」。

![](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/ef35d915a3e7434bbf88d98ff0de95c4~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5aSn5rygX3czY3BsdXNjb20=:q75.awebp?rk3s=f64ab15b&x-expires=1775995265&x-signature=z760b%2FfyMyqwcXM3Ikhs5H2FmT0%3D)

> Demo 地址:[codepen.io/airen/full/…](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fairen%2Ffull%2FMYjXyaY)

溫馨提示,有關於 dialog 更詳細的介紹,請移步閱讀《[用於美化模態框的 :modal 和 ::backdrop](https://juejin.cn/book/7223230325122400288/section/7229323379793199104)》!

Popover API vs. dialog 元素:有什麼差別?
-----------------------------------

在 Popover API 和 dialog 元素看起來都在處理「彈出層」,但本質上並不是同一類問題。兩者在可存取性與互動模式上存在明顯差異,這也是它們各自適用場景不同的根本原因。Popover API 更偏向輕量、非阻塞的浮層,而 dialog 則是為嚴格的模態互動所設計。

或者說,兩者核心差異在於頁面其他部分是否仍然可以互動:

| 功能 | Popover API | dialog 元素 |
|---|---:|---:|
| 阻止背景互動 | ❌ | ✅ |
| 點擊外部關閉 | ✅ | ❌ |
| 焦點陷阱 | ❌ | ✅ |
| 捲動鎖定 | ❌ | ✅ |
| 適用場景 | Tooltip、下拉選單、Toast 等 | 確認框、表單、警告等 |
| API 使用方式 | 純宣告式 HTML | 需要 JavaScript(.showModal()) |

兩者的控制模型也體現了這一差異。Popover API 完全不需要 JavaScript —— popovertarget 就能在 HTML 中把按鈕和面板綁在一起。dialog 如果只用 .show() 打開,則是非模態框,幾乎沒有實際用處;真正的模態框(具有模態行為)依賴 .showModal()。兩者都可以用 Escape 關閉,但只有 dialog 會捕獲焦點並在關閉前阻止與頁面其他部分的互動。

因此,在實際使用中可以遵循一個簡單原則:大多數普通的彈出層場景優先選擇 Popover API;只有當你需要一個真正阻止使用者與頁面其他部分互動的模態對話框時,才使用 dialog。

details 和 summary
---------------------

![](https://i.imgur.com/GVIWCri.jpeg)

原生 <details></details> 和 <summary></summary> 提供了一種極其直接的方式來實作展開與摺疊的互動模式。像 FAQ、手風琴、可摺疊資訊區這類 UI,過去往往需要透過 JavaScript 控制狀態切換、管理類別甚至處理動畫,而現在,我們可以用一段簡潔的原生 HTML 完成。

<div><div><div></div><span>HTML</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>details</span>></span>
<span> <span>summary</span>></span>查看詳細內容<span><span>summary</span>></span>
<span> <span>p</span>></span>這裡是展開後的內容,使用者可以點擊 summary 來切換顯示。<span><span>p</span>></span>
<span><span><span>details</span>></span></span>

更重要的是,這不僅僅是「更簡單」的實作方式,它還是一種語意化且可存取的解決方案。瀏覽器會自動處理展開狀態(open 屬性)、鍵盤互動(如 Enter / Space 切換)、以及與螢幕閱讀器的相容性。這表示你不再需要手動管理 aria-expanded、焦點狀態或鍵盤事件,這些過去容易出錯的細節,現在都由瀏覽器原生保障。

從可擴充性來看,<details></details> 也並不侷限於基礎用法。你可以透過 CSS 精細控制它的外觀,例如自訂 summary 的樣式、隱藏預設箭頭、甚至結合動畫實現更流暢的展開效果。對於手風琴場景,還可以透過少量 CSS(或配合 :has() 等現代選擇器)實作「同一時間只展開一個」的互動,而無需 JavaScript。

Demo 地址:codepen.io/airen/full/…

在瀏覽器支援方面,<details></details> 和 <summary></summary> 已成為現代瀏覽器的基礎能力,可以放心用於生產環境。而在開發體驗上,它幾乎是「零成本」的:無需引入函式庫、無需編寫邏輯,就能獲得完整的互動與可存取性支援。

對於絕大多數摺疊類 UI 場景,這通常是最簡單、最可靠、也最推薦的實作方式。

溫馨提示,有關這方面更詳細的介紹,請移步閱讀《CSS 佈局:建立手風琴元件》和《Web UI:使用現代 CSS 建立手風琴元件》!

需要特別指出的是,發展到今天,很多「內容切換」類的互動,其實已經不再依賴 JavaScript。借助現代 CSS 與原生 HTML 能力,你完全可以用純 CSS(或極少量宣告式 HTML)實現這些效果。常見的方式包括:利用 :has() 搭配 input(checkbox / radio)實現狀態驅動的切換,使用 <dialog></dialog> 構建模態互動,透過 Popover API 控制浮層顯示,以及 <details></details> 和 <summary></summary> 實作摺疊內容等。

這些方案不僅實作更簡單,而且更加語意化、可維護,並且在可存取性方面由瀏覽器原生保障。如果你對這類「用 CSS 替代 JavaScript」的實作方式感興趣,可以進一步閱讀《CSS 佈局:切換內容的現代方式》深入了解。

捲動驅動動畫

在過去,所謂「捲動驅動動畫」,幾乎等同於一套固定模式。用 JavaScript 監聽 scroll 事件,配合 requestAnimationFrame,在每一幀中讀取 window.scrollY,再去更新 CSS 自訂屬性或 transform、opacity 等屬性值。隨著頁面複雜度的提升,這種做法很容易帶來效能問題——頻繁的主執行緒計算、版面抖動,以及難以維護的同步邏輯。像 GSAP ScrollTrigger 這樣的函式庫,本質上就是為了讓這種模式更可控、更易管理而存在。

而現在,CSS 正在接管這一切。隨著捲動驅動動畫規範的引入,你可以透過 animation-timeline(scroll() 或 view())直接將動畫綁定到捲動進度上。也就是說,動畫不再依賴 JavaScript 去驅動,而是由瀏覽器根據捲動狀態自動計算和執行——整個過程完全宣告式,無需額外腳本。

<div><div><div></div><span>CSS</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>@keyframes</span> spin {</span>
<span>    <span>to</span> {</span>
<span>        rotate: y <span>5turn</span>;</span>
<span>    }</span>
<span>}</span>
<span></span>
<span><span>@supports</span> (<span>animation-timeline</span>: scroll()) {</span>
<span>    <span>@media</span> (<span>prefers-reduced-motion</span>: no-preference) {</span>
<span>        <span>figure</span> {</span>
<span>            <span>animation</span>: spin linear both;</span>
<span>            <span>animation</span>-timeline: <span>scroll</span>();</span>
<span>        }</span>
<span>    }</span>
<span>}</span>

![](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/6c50fe2c3a724ed4af40f24184e5afb4~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5aSn5rygX3czY3BsdXNjb20=:q75.awebp?rk3s=f64ab15b&x-expires=1775995265&x-signature=ssU3cyEUc3jzMkaQ60dLZBoyOt8%3D)

> Demo 地址:[codepen.io/airen/full/…](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fairen%2Ffull%2FjErNVwg)

這種變化的關鍵在於執行方式的不同:動畫運行在瀏覽器的合成執行緒上,而不是主執行緒。這意味著即使在複雜頁面或高負載情況下,動畫依然可以保持流暢,不會因為 JavaScript 阻塞而掉幀。從「手動驅動」到「瀏覽器原生驅動」,這正是 CSS 在這一類互動能力上的一次質變。

更重要的是,CSS 捲動驅動動畫的意義,早已不只是「做捲動動畫」這麼簡單。它本質上提供了一種基於捲動狀態驅動 UI 的能力,而這正是過去大量依賴 JavaScript 才能實現的互動核心。

借助這些能力,你可以用純 CSS 實現一整類複雜效果。例如:根據捲動位置判斷內容狀態(如文本是否溢位、數量或尺寸變化)、構建捲動進度指示器、(TOC)自動高亮當前章節、實作捲動遮罩效果,甚至是更具表現力的互動——像 macOS Dock 的圖示放大效果、按鈕在捲動中切換為導覽列、元素的漸入漸出動畫、帶漸層效果的捲軸、滑動刪除、3D 翻轉、捲動視差,以及輪播、CoverFlow 等元件。

這些過去需要大量事件監聽、狀態同步和手動計算的邏輯,現在可以直接透過 CSS 宣告式地完成。你不再需要「監聽捲動再驅動 UI」,而是把「捲動本身」作為動畫時間軸,讓瀏覽器去完成剩下的一切。這種思維方式的轉變,才是它真正強大的地方。

> 溫馨提示,如果你想進一步玩轉 CSS 捲動驅動動畫,不妨看看《[CSS 捲動驅動動效](https://juejin.cn/book/7223230325122400288/section/7259272255786450981)》和《[Web 動效:捲動驅動動效之實戰技巧](https://juejin.cn/book/7395119173656903715/section/7446281443421978651)》課程!

捲動狀態查詢
------

![](https://i.imgur.com/DyMXuxx.jpeg)

CSS 捲動狀態查詢可以看作是對容器查詢的一次重要擴展(容器查詢在捲動狀態查詢之前已有尺寸容器查詢和樣式容器查詢)。它將由瀏覽器內部管理的捲動狀態直接暴露給 CSS,使得 Web 開發者可以基於捲動行為進行樣式控制,而完全不需要借助 JavaScript。該特性已在 Google Chrome 133 中發布,Mozilla Firefox 和 Safari 的支援也在推進中。

這一功能的核心在於,它提供了三類常見但過去必須依賴 JavaScript 才能取得的捲動狀態:stuck(粘性狀態)、snapped(吸附狀態)和 scrollable(可滾動狀態)。

首先是 stuck。對於 position: sticky 元素,過去如果想知道它是否已經「吸附」在頂部,通常需要借助一個額外的哨兵元素,再配合 IntersectionObserver 來判斷。而現在,只需宣告 container-type: scroll-state,就可以在 CSS 中直接根據是否處於黏性狀態來套用樣式,例如在吸頂時添加陰影效果。這種從「監聽狀態」到「宣告狀態」的轉變,大大簡化了實作方式。

<div><div><div></div><span>CSS</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>.stuck-top</span> {</span>
<span> <span>/ 粘性定位,是必須要的 /</span></span>
<span> <span>position</span>: sticky;</span>
<span> <span>top</span>: <span>0</span>;</span>
<span> </span>
<span> span>@supports</span (<span>container-type</span>: scroll-state) {</span>
<span> container-type: scroll-state; <span>/ 定義捲動狀態查詢容器 /</span></span>
<span> </span>
<span> <span>.heading</span> {</span>
<span> <span>transition</span>: box-shadow <span>0.5s</span> ease-out;</span>
<span> </span>
<span> span>@container</span scroll-state(<span>stuck</span>: top) {</span>
<span> <span>box-shadow</span>: </span>
<span> <span>rgb</span>(<span>0</span> <span>0</span> <span>0</span> / <span>0.6</span>) <span>0px</span> <span>12px</span> <span>28px</span> <span>0px</span>,</span>
<span> <span>rgb</span>(<span>0</span> <span>0</span> <span>0</span> / <span>0.1</span>) <span>0px</span> <span>2px</span> <span>4px</span> <span>0px</span>,</span>
<span> <span>rgb</span>(<span>255</span> <span>255</span> <span>255</span> / <span>0.05</span>) <span>0px</span> <span>0px</span> <span>0px</span> <span>1px</span> inset;</span>
<span> }</span>
<span> }</span>
<span> }</span>
<span>}</span>

其次是 snapped。在捲動捕捉場景中,過去要判斷哪個元素當前被吸附,需要監聽 scrollsnapchange 事件,並手動切換類名。而現在,被吸附的元素可以直接透過 CSS 感知自身狀態,從而改變樣式或影響其子元素。例如:

<div><div><div></div><span>CSS</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>.list</span> {</span>
<span>    <span>scroll-snap-type</span>: x mandatory;</span>
<span>    <span>overflow-x</span>: auto;</span>
<span>    <span>scroll-padding-inline</span>: <span>2ch</span>;</span>
<span></span>
<span>    <span>li</span> {</span>
<span>        <span>scroll-snap-align</span>: center;</span>
<span></span>
<span>        <span>@supports</span> (<span>container-type</span>: scroll-state) {</span>
<span>            container-type: scroll-state;</span>
<span>    </span>
<span>            <span>:is</span>(<span>.blur</span>, <span>.content</span>) {</span>
<span>                translate: <span>0</span> <span>100%</span>;</span>
<span>                <span>transition</span>: translate <span>0.2s</span> ease-in-out;</span>
<span>                </span>
<span>                <span>@container</span> scroll-state(<span>snapped</span>: x) {</span>
<span>                    translate: <span>0</span>;</span>
<span>                }</span>
<span>            }</span>
<span>    </span>
<span>            <span>figure</span> <span>img</span> {</span>
<span>                <span>@container</span>  scroll-state(<span>snapped</span>: x) {</span>
<span>                    <span>mix-blend-mode</span>: darken;</span>
<span>                    scale: <span>1.5</span>;</span>
<span>                }</span>
<span>                </span>
<span>                <span>@container</span> <span>not</span>  scroll-state(<span>snapped</span>: x){</span>
<span>                    <span>filter</span>: <span>grayscale</span>(<span>1</span>);</span>
<span>                }</span>
<span>            }</span>
<span>        }</span>
<span>    }</span>
<span>}</span>

![](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/1a008cf83f6b432b96d4021969a0c1ab~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5aSn5rygX3czY3BsdXNjb20=:q75.awebp?rk3s=f64ab15b&x-expires=1775995265&x-signature=RPMMTAcFFANEEE3oD%2FYceip8ezk%3D)

> Demo 地址:[codepen.io/airen/full/…](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fairen%2Ffull%2FZYYjNLm)

最後是 scrollable。像「只有在內容可滾動時才顯示捲動陰影」或「捲動到一定位置才出現返回頂端按鈕」這類常見需求,以往通常依賴捲動監聽或觀察器 API。現在,這些都可以透過 CSS 條件直接表達——當某個方向不可滾動時隱藏對應 UI,從而實現更簡潔、宣告式的控制。

<div><div><div></div><span>CSS</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>.scroll-container</span> {</span>
<span> <span>overflow-y</span>: auto;</span>
<span> <span>overflow-x</span>: hidden;</span>
<span> <span>scroll-snap-type</span>: y mandatory;</span>
<span> <span>inline-size</span>: <span>30em</span>;</span>
<span> <span>block-size</span>: <span>18.735</span>lh;</span>
<span> <span>border</span>: <span>1px</span> solid <span>var</span>(--surface-<span>1</span>);</span>
<span> <span>scroll-padding-block</span>: <span>10px</span>;</span>
<span> overscroll-behavior: contain;</span>
<span> <span>display</span>: grid;</span>
<span></span>
<span> > * {</span>
<span> <span>grid-area</span>: <span>1</span>/<span>1</span>;</span>
<span> }</span>
<span></span>
<span> > <span>.scroll-indicator</span> {</span>
<span> place-self: end;</span>
<span> <span>position</span>: sticky;</span>
<span> inset-block-end: <span>10px</span>;</span>
<span> <span>inline-size</span>: <span>100%</span>;</span>
<span> <span>text-align</span>: center;</span>
<span> <span>transition</span>: translate <span>0.2s</span> ease;</span>
<span></span>
<span> > svg {</span>
<span> <span>background</span>: <span>var</span>(--surface-<span>2</span>);</span>
<span> aspect-ratio: <span>1</span>;</span>
<span> <span>border-radius</span>: <span>1</span>e3px;</span>
<span> <span>inline-size</span>: <span>48px</span>;</span>
<span> <span>block-size</span>: <span>48px</span>;</span>
<span> }</span>
<span> }</span>
<span></span>
<span> span>@supports</span (<span>container-type</span>: scroll-state) {</span>
<span> container-type: scroll-state size;</span>
<span></span>
<span> > <span>.scroll-indicator</span> {</span>
<span> span>@container</span scroll-state((<span>scrollable</span>: top) <span>or</span> (<span>not</span> (<span>scrollable</span>: bottom))) {</span>
<span> translate: <span>0</span> <span>calc</span>(<span>100%</span> + <span>10px</span>);</span>
<span> }</span>
<span> </span>
<span> span>@container</span scroll-state((<span>scrollable</span>: top) <span>and</span> (<span>not</span> (<span>scrollable</span>: bottom))) {</span>
<span> translate: <span>0</span> <span>calc</span>(<span>100%</span> + <span>10px</span>);</span>
<span> rotate: <span>0.5turn</span>;</span>
<span> }</span>
<span> }</span>
<span> }</span>
<span></span>
<span> <span>.item</span> {</span>
<span> <span>scroll-snap-align</span>: start;</span>
<span> <span>scroll-snap-stop</span>: always;</span>
<span> }</span>
<span>}</span>

Demo 地址:codepen.io/airen/full/…

這三類狀態查詢,本質上替代了過去大量基於事件監聽(scroll)或觀察器(IntersectionObserver)的實作方式。你不再需要「監聽捲動再更新狀態」,而是可以直接在 CSS 中描述「當處於某種捲動狀態時應該如何表現」,讓瀏覽器去完成剩餘的工作。這正是現代 CSS 在互動能力上的又一次重要躍遷。

溫馨提示:如果你希望更深入理解捲動狀態查詢的原理與實際應用,建議進一步閱讀《Web UI:容器查詢之捲動狀態容器查詢》和《Web UI:CSS 中的捲動驅動方向》這兩篇內容。它們會從基礎概念到進階實作,幫助你全面掌握這一能力,並將其靈活運用到真實專案中。

更有趣的是,如今結合 CSS 的捲動驅動動畫與捲動捕捉等能力,你已經可以用純 CSS 構建複雜的互動元件。像 Web 上常見的輪播(Carousel)甚至 CoverFlow 這類具有空間感和動態效果的元件,過去往往需要一整套 JavaScript 邏輯來驅動,而現在則可以透過宣告式的方式直接實現。例如,透過捲動捕捉控制捲動對齊,再配合捲動驅動動畫綁定捲動進度,你可以輕鬆實作類似 Nintendo Switch 主畫面那種帶有縮放、聚焦和過渡效果的輪播體驗:

Demo 地址:codepen.io/airen/full/…

需要特別提出的是,CSS 正在引入一種新的查詢能力——錨定容器查詢。它是在尺寸容器查詢、樣式容器查詢以及捲動狀態查詢之後的又一次擴展,進一步增強了 CSS 對「上下文感知」的能力。

當你結合 CSS 錨點定位使用時,這種能力會變得尤其強大。透過錨定容器查詢,你可以讓元件根據相對於錨點的位置變化自動調整自身表現,例如在發生位置回退時切換樣式,而這一切都可以在 CSS 中宣告完成。

Demo 地址:codepen.io/airen/full/…

換句話說,過去需要透過 JavaScript 偵測位置、監聽變化並手動更新 UI 的邏輯,現在可以完全交由 CSS 處理。這使得複雜的浮層、提示框以及自適應互動介面,能夠以更簡潔、更穩定的方式實現,真正做到「由 CSS 驅動 UI 行為」。有關這方面更詳細的介紹,請移步閱讀《Web 元件:一種基於錨定容器查詢的動態切換提示方案》!

視圖過渡

在過去,單頁應用(SPA)的頁面切換動畫——也就是路由切換時「舊頁面淡出,新頁面淡入」的效果,幾乎完全依賴 JavaScript。無論是 React 的 react-transition-group,還是 Vue 的 <transition></transition> 元件,本質上都在幫你管理一套流程:複製元素、設定絕對定位、同步多個動畫狀態,並手動控制 opacity 和 transform 的時間軸。這不僅實作成本高,而且維護起來也相當繁瑣。

而 CSS 視圖過渡(CSS View Transition API)將這一切交還給瀏覽器來完成。你只需要用 document.startViewTransition() 包裹一次 DOM 狀態更新,瀏覽器就會自動捕捉更新前後的介面狀態,並在兩者之間執行平滑過渡(例如交叉淡入淡出)。整個過程無需手動管理中間狀態,同時你仍然可以透過 CSS 自定義動畫效果。

對於同一文件內的狀態切換,這幾乎就是完整的 API 使用方式。而在跨文件導航(也就是真正的頁面跳轉)中,只需一行 CSS —— @view-transition {navigation: auto} —— 即可啟用類似的過渡效果,讓多頁面應用也能擁有接近 SPA 的流暢體驗。

更進一步,命名視圖過渡還允許你針對特定元素建立跨頁面的動畫,例如卡片從列表頁「展開」進入詳細頁。這類效果在過去通常需要複雜的版面複製與同步技巧,而現在可以用更直觀的方式實作。

在瀏覽器支援方面,Google Chrome 已率先支援該特性,而 Safari 和 Mozilla Firefox 也在持續推進中。整體來看,這標誌著頁面過渡動畫正從「框架能力」逐步轉變為「平台能力」。

Demo 地址:codepen.io/airen/full/…

時至今日,CSS 視圖過渡(View Transitions)已逐漸成為一種通用的介面狀態過渡機制。借助它,你可以輕鬆實作諸如主題切換(如淺色/深色模式的平滑過渡)、展開與摺疊的動效,甚至是帶有氛圍感的動態燈光效果等。這些原本需要精細控制時間軸和狀態同步的互動,現在都可以交由瀏覽器統一處理。

這種能力的關鍵在於,它將「狀態變化」與「視覺過渡」解耦:你只需要關心介面更新本身,而動畫如何發生、如何銜接,則由瀏覽器自動完成,同時又允許你透過 CSS 精細訂製細節。這讓複雜動效的實作變得更自然,也更易於維護。

如果你想進一步探索這些技巧和實際應用場景,可以移步閱讀《解鎖 CSS View Transitions API 的魔力》,深入了解這一能力的更多可能性。

自訂下拉選擇框

自訂下拉選擇框,幾乎是 Web 上被「重複造輪子」最多的 UI 元件之一。長期以來,由於原生 <select></select> 難以進行深度樣式化,各大設計系統不得不從零實作一整套替代方案:隱藏原生 <select></select> 以保留可存取性,用自訂元件作為觸發器,手動定位下拉清單,實作鍵盤導覽、焦點管理,並為螢幕閱讀器補充語意支援。為了替代一個基礎的表單控件,往往需要成百上千行程式碼,這本身就是平台能力缺失的體現。

而「可定製下拉選擇框」正是為了解決這個長期痛點。它允許 Web 開發者透過 CSS 直接控制 <select></select> 以及其內部結構。不但可以自訂觸發按鈕的外觀,還可以樣式化下拉容器,甚至精細控制每一個 <option></option>。更進一步,你還可以在選項中使用任意 HTML 內容,讓下拉選擇框具備更豐富的表達能力。

<div><div><div></div><span>CSS</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>@supports</span> (<span>appearance</span>: base-select) {</span>
<span>    select {</span>
<span>        anchor-name: --select;</span>
<span>        <span>padding-block</span>: <span>0</span>;</span>
<span>        </span>
<span>        &::picker-icon {</span>
<span>            <span>display</span>: none;</span>
<span>        }</span>
<span>      </span>
<span>        <span>.icon</span> {</span>
<span>            <span>width</span>: <span>calc</span>(<span>var</span>(--option-size) * <span>0.375</span>);</span>
<span>            <span>height</span>: <span>calc</span>(<span>var</span>(--option-size) * <span>0.625</span>);</span>
<span>        }</span>
<span>    }</span>
<span>    </span>
<span>    <span>/* 美化下拉框 */</span></span>
<span>    ::<span>picker</span>(select) {</span>
<span>        <span>--counts</span>: <span>6</span>;</span>
<span>        <span>--rotation-divide</span>: <span>calc</span>(<span>360deg</span> / <span>var</span>(--counts));</span>
<span>        <span>position</span>-anchor: --select;</span>
<span>        <span>top</span>: <span>anchor</span>(center);</span>
<span>        <span>left</span>: <span>anchor</span>(center);</span>
<span>        translate: -<span>50%</span> -<span>50%</span>;</span>
<span>        <span>overflow</span>: visible;</span>
<span>        <span>transition</span>: overlay <span>0.5s</span>, display <span>0.5s</span>;</span>
<span>        <span>transition</span>-behavior: allow-discrete;</span>
<span>        <span>margin</span>: <span>0</span>;</span>
<span>        <span>padding</span>: <span>0</span>;</span>
<span>        <span>background</span>: transparent;</span>
<span>        <span>border</span>: none;</span>
<span>    }</span>
<span>}</span>

這項能力的意義在於,它將一個「必須用 JavaScript 重寫」的元件,重新帶回到原生平台層面。你不再需要在「可存取性」和「設計一致性」之間做取捨,而是可以同時獲得兩者。

目前,Google Chrome 已在實驗性旗標下提供了初步實作。雖然規範仍在持續演進中,但整體方向已非常清晰:未來,自訂下拉選擇框將不再是複雜的工程問題,而只是一次普通的 CSS 樣式設計。

![](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/4351dde4a0794d15acd5e1ee257b4429~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5aSn5rygX3czY3BsdXNjb20=:q75.awebp?rk3s=f64ab15b&x-expires=1775995265&x-signature=ZfmJvMY2MtMB%2B%2FmZ5j4Jc8V8EOs%3D)

> Demo 地址:[codepen.io/airen/full/…](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fairen%2Ffull%2FKwwrGjQ)

溫馨提示:如果你想更深入了解自訂下拉選擇框的實作原理與進階用法,可以進一步閱讀相關內容,深入探索這一特性的更多可能性與實踐技巧。

- [CSS 圖形:自訂下拉選擇框](https://juejin.cn/book/7395119173656903715/section/7411330693440995340)
- [Web UI:美化下拉選擇框](https://link.juejin.cn?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F2009655521020503701)
- [Web UI:使用現代 CSS 自訂下拉選擇框](https://juejin.cn/book/7395119173656903715/section/7503042683016609803)
- [Web UI:使用現代 CSS 自訂下拉選擇框(Part2)](https://juejin.cn/book/7395119173656903715/section/7592211295034687531)
- [Web UI:使用現代 CSS 自訂下拉選擇框(Part3)](https://juejin.cn/book/7395119173656903715/section/7600334628813668387)

焦點組
---

![](https://i.imgur.com/DkUVRoL.jpeg)

在組合型控件(如工具列、標籤列、單選群組或選單)中實作鍵盤的箭頭導覽,一直是一項重複且繁瑣的工作。傳統做法通常需要撰寫大量 JavaScript,比如監聽 keydown 事件、判斷 ArrowRight、ArrowLeft、ArrowUp、ArrowDown,手動維護 tabindex,並在使用者透過 Tab 重新進入元件時,記住上一次的焦點位置。幾乎每一個 UI 函式庫都有自己的一套實作方案,例如在 React 中常見的 roving-tabindex 模式,或 Fluent UI 提供的 FocusZone,本質上都在解決同一個問題。

而 Open UI 提出的 focusgroup 屬性,試圖將這一整套邏輯宣告式地交還給瀏覽器。只需要在容器元素上添加一個屬性,瀏覽器就可以自動管理其內部可聚焦子元素之間的箭頭鍵導覽,無需任何 JavaScript 參與。這不僅簡化了實作,也讓行為更加一致和可預測。

<div><div><div></div><span>HTML</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>div</span> <span>role</span>=<span>"toolbar"</span> <span>focusgroup</span> <span>aria-label</span>=<span>"Text Formatting"</span>></span>
<span> <span>button</span> <span>type</span>=<span>"button"</span> <span>tabindex</span>=<span>"-1"</span>></span>Bold<span><span>button</span>></span>
<span> <span>button</span> <span>type</span>=<span>"button"</span> <span>tabindex</span>=<span>"-1"</span>></span>Italic<span><span>button</span>></span>
<span> <span>button</span> <span>type</span>=<span>"button"</span> <span>tabindex</span>=<span>"-1"</span>></span>Underline<span><span>button</span>></span>
<span><span><span>div</span>></span></span>

同時,focusgroup 還提供了一些可選配置,用來微調導覽行為。例如,可以透過 inline 或 block 限制導覽方向(僅水平或垂直),使用 wrap 實現循環導覽,以及透過 no-memory 控制每次重新聚焦時是否回到初始項,而不是上一次的焦點位置。

<div><div><div></div><span>HTML</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>div</span> <span>role</span>=<span>"tablist"</span> <span>focusgroup</span>=<span>"inline wrap no-memory"</span>></span>
<span>    <span>button</span> <span>role</span>=<span>"tab"</span> <span>tabindex</span>=<span>"0"</span> <span>aria-selected</span>=<span>"true"</span>></span>Mac<span><span>button</span>></span>
<span>    <span>button</span> <span>role</span>=<span>"tab"</span> <span>tabindex</span>=<span>"-1"</span> <span>aria-selected</span>=<span>"false"</span>></span>Windows<span><span>button</span>></span>
<span>    <span>button</span> <span>role</span>=<span>"tab"</span> <span>tabindex</span>=<span>"-1"</span> <span>aria-selected</span>=<span>"false"</span>></span>Linux<span><span>button</span>></span>
<span><span><span>div</span>></span></span>

更進一步,這一機制還支援巢狀結構。例如,一個水平的選單列內部包含垂直的子選單,每個層級都可以擁有獨立的導覽軸向,讓不同方向的箭頭鍵各司其職,形成更自然的互動體驗。

目前,focusgroup 尚處於提案階段,尚未被瀏覽器正式實作。不過其更具體的演進版本正在積極推進中。考量到它所替代的模式在 Web 開發中極為普遍,這項能力很可能會成為未來原生平台的一部分。

瀑布流佈局
-----

![](https://i.imgur.com/M1eVP0c.jpeg)

像 Pinterest 這種瀑布流佈局(高度不一的卡片按列緊密排列、自動填補空隙),一直是 Web 上的經典佈局需求。但長期以來,這類佈局幾乎只能依賴 JavaScript 實作。像 Masonry.js、Isotope 這樣的函式庫之所以流行,正是因為傳統的 CSS Grid 無法原生支援這種「跨列填充」的佈局方式——Grid 項目始終遵循嚴格的行列軌道,無法自動回填空白。

而 CSS 正在補上這一塊能力。最初的 Masonry 提案透過 grid-template-row: masonry(以及對應的列方向變體),讓瀏覽器自動計算並填補佈局中的空隙,從而實現真正意義上的瀑布流佈局,無需額外的腳本介入。

<div><div><div></div><span>CSS</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>.masonry</span> {</span>
<span> <span>display</span>: grid;</span>
<span> <span>grid-template-columns</span>: <span>repeat</span>(<span>3</span>, <span>1</span>fr);</span>
<span> <span>grid-template-rows</span>: masonry;</span>
<span> <span>gap</span>: <span>16px</span>;</span>
<span>}</span>

不過,規範的發展並沒有止步於此。隨著討論的推進,這一能力被重新設計並更名為 Grid Lanes,強調其作為 CSS Grid 體系的一部分,而不是一個全新的 display 類型。新的方向是透過類似 grid-lanes 的機制,讓佈局在既有 Grid 模型中具備「跨軌道填充」的能力,從而實現更靈活的排布方式。

<div><div><div></div><span>CSS</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>.masonry</span> {</span>
<span>    <span>display</span>: grid-lanes;</span>
<span>    <span>grid-template-columns</span>: <span>repeat</span>(<span>3</span>, <span>1</span>fr);</span>
<span>    <span>gap</span>: <span>16px</span>;</span>
<span>}</span>

這一轉變的意義在於,它不只是「讓瀑布流變得可用」,而是將這一能力系統性地整合到 CSS 網格佈局體系中。Web 開發者不再需要依賴 JavaScript 在佈局完成後再進行二次計算(這也是導致佈局抖動和效能問題的根源),而是可以直接交由瀏覽器在佈局階段完成。

目前,Mozilla Firefox 已在實驗性旗標下提供相關實作,Google Chrome 也在積極推進中。雖然規範仍在演進,但方向已非常明確:未來,瀑布流佈局將成為 CSS 的原生能力,而不是一個需要額外函式庫來「修補」的特例。

![](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/24a20d168d454c6c9b4c853fa091dc08~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5aSn5rygX3czY3BsdXNjb20=:q75.awebp?rk3s=f64ab15b&x-expires=1775995265&x-signature=tuZKVk53Ki97zqEYDcc846QWGDg%3D)

> Demo 地址:[codepen.io/airen/full/…](https://link.juejin.cn?target=https%3A%2F%2Fcodepen.io%2Fairen%2Ffull%2FogLwgqJ)

溫馨提示:如果你想更深入了解瀑布流佈局相關的實作原理與實踐方式,可以進一步閱讀《[CSS Grid 之瀑布流佈局:`masonry` 和 `masonry-auto-flow`](https://juejin.cn/book/7223230325122400288/section/7259668568885706813)》和《[CSS 佈局:從 Masonry Layout 到 Grid Lanes——CSS Grid 的新能力](https://juejin.cn/book/7395119173656903715/section/7597427095630774298)》以取得更詳細的講解。同時,如果你希望系統性掌握現代 Web 版面配置(包括 Grid、Flexbox 以及最新佈局能力)的設計思路與實戰技巧,也可以參考我的小冊《現代 Web 版面配置》。

field-sizing
--------------

![](https://i.imgur.com/J8PXzR2.jpeg)

field-sizing: content 為表單元素帶來了一項看似簡單卻非常實用的能力:讓 <input></input>、<select></select> 和 <textarea></textarea> 可以根據內容自動調整尺寸。也就是說,輸入多少內容,欄位就擴展多少空間,無需額外干預。

在過去,如果你想實作一個「自動增高」的 textarea,通常需要借助 JavaScript 監聽輸入事件、讀取 scrollHeight,然後手動更新高度。這不僅程式碼繁瑣,還容易引發效能與佈局問題。而現在,這一切都可以透過一行 CSS 完成:

<div><div><div></div><span>CSS</span></div><div><div> <span>體驗AI代碼助手</span></div><div> <span>代碼解讀</span></div><div>複製代碼</div></div></div>```
<span><span>textarea</span> {</span>
<span> field-sizing: content;</span>
<span>}</span>

Demo 地址:codepen.io/airen/full/…

這種變化的意義在於,它將一個常見但瑣碎的互動需求,從「腳本邏輯」轉變為「樣式宣告」。你不再需要關心何時更新尺寸,也不需要處理邊界情況,瀏覽器會根據內容自動完成計算與渲染。

目前,Google Chrome 已在 2024 年支援該特性。雖然它只是一個相...


原文出處:https://juejin.cn/post/7624562186795859968


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝7   💬9   ❤️3
597
🥈
我愛JS
📝2   💬7   ❤️2
205
🥉
💬1  
4
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登