如果你曾經擁有過潘通色卡——那種平面設計師過去視若珍寶的色卡——你就會明白,這些卡片從一個鉚釘點向外展開時那種令人愉悅的感覺。每張卡片都沿著自己的弧線擺動,你可以用手翻閱它們。
我想在本週的 CodePen 挑戰中重現那種體驗,本週的挑戰主題是調色板。
我們將一起建立一個完全互動的彩色扇形卡組,其中卡組的展開會根據容器的寬度進行調整,卡片會知道自己在同組卡片中的位置,點擊卡片進行「聚焦」操作完全由瀏覽器的原生<details>元素處理。
無需 JavaScript!讓我們開始吧!
我們的卡片組是一個<section> ,其中包含一張封面卡,後面跟著顏色卡,每張顏色卡都包裹在一個<details>元素中:
<section>
<!-- cover card -->
<details name="deck">
<summary>Reds <span>×</span></summary>
<ul>
<li style="--c: lab(45% 67 30)">
<strong>Poppy Red</strong>
<dl>
<dt>HEX</dt><dd>#DC3D4C</dd>
<dt>RGB</dt><dd>220, 61, 76</dd>
<dt>LAB</dt><dd>45, 56, 25</dd>
</dl>
</li>
<!-- more colors -->
</ul>
</details>
<details name="deck">
<summary>Blues <span>×</span></summary>
<!-- ... -->
</details>
<!-- more cards -->
</section>
需要注意以下幾點:
封面牌不會翻轉——它就放在牌堆的最前面。
每張顏色卡片都是一個<details name="deck">元素。 name name是關鍵-它使它們形成一個獨立的折疊面板。一次只能打開一張,點擊已開啟的卡片即可關閉。
<summary>既是卡片標籤,也是點擊目標。
目前還沒什麼特別的。讓我們來加入一些 CSS 程式碼:

這裡我就不深入講解 CSS 了;它只是一個<ul> ,顏色值在<dl>中定義,並用網格包裹起來。
首先,我們需要將所有卡片放置在同一個網格單元格中,並且彼此堆疊:
section {
container-type: inline-size;
display: grid;
place-items: end center;
}
section > * {
grid-area: 1 / -1;
z-index: calc(sibling-count() - sibling-index());
}
在<section>元素上設定container-type: inline-size可以讓我們稍後使用容器查詢單位。每個直接子元素都會被放置在同一個網格單元格中,網格面積設定為grid-area: 1 / -1 ,從而形成一個堆疊結構。
z-index行使用了兩個新的 CSS 函數——sibling sibling-count()和sibling-index() ——來確保第一張卡片位於最頂層。第一個子元素的sibling-index()值為 1,因此它擁有最高的z-index 。最後一個子元素的 z-index 值也為 1。這種自然的堆疊順序——沒有硬編碼值、沒有計數器、也沒有 JavaScript。
所以,目前我們只能看到封面卡——顏色卡隱藏在它後面(鉚釘是一個帶有radial-gradient ::after偽元素):

progress()有趣的地方就在這裡。真正的扇形卡在空間充足時會展開,在狹窄空間則會折疊成緊湊的堆疊狀。我們希望實現同樣的效果——而新的 CSS progress()函數讓這一切變得優雅:
section > * {
--spread: progress(100cqi, 300px, 1440px);
--end-degree: calc(var(--spread) * 45deg);
--start-degree: calc(var(--spread) * -45deg);
}
progress()根據值在指定範圍內的位置傳回0到1之間的值。例如, progress(100cqi, 300px, 1440px)詢問:“容器的行內尺寸在 300px 和 1440px 之間有多大差距?”
在 300px 或以下: --spread為0 — 無扇形,卡片平放堆疊。
在 1440px 或更高解析度下: --spread為1 — 完全扇形,卡片跨度從 -45° 到 +45°。
在 870px(中點)處: --spread為0.5 — 半扇形。
無需@container查詢,只需一行 CSS,即可實現持續響應式佈局。
sibling-index()定位每張卡片現在每張牌都需要自己的旋轉角度,根據其在牌組中的位置,在--start-degree和--end-degree之間進行插值:
section > * {
rotate: calc(
var(--start-degree) +
(var(--end-degree) - var(--start-degree)) *
(sibling-index() - 1) / (sibling-count() - 1)
);
transform-origin: calc(100% - var(--rivet)) calc(100% - var(--rivet));
}
讓我們來詳細分析一下:
sibling-index() - 1表示從零開始的索引位置(0 表示第一張卡片,1 表示第二張卡片,依此類推)。
sibling-count() - 1表示卡片之間的「間隙」總數。
將它們相除,即可得到每張卡片位置的進度值,範圍從0到1
我們將該值乘以角度範圍,再加上起始偏移量。
transform-origin設定為右下角——偏移量為--rivet因此所有卡片都圍繞著同一個樞軸點旋轉,就像帶有鉚釘銷的實體扇形卡組一樣。
太棒了!現在卡片會從一個點呈扇形展開,展開方式會根據容器寬度自動調整,但目前它們還不能互動。
現在我們有了:

讓我們調整瀏覽器視窗大小:

我覺得這真是太令人滿足了!
<details>這就是<details> 元素發揮作用的地方。透過將所有顏色卡片name="deck"` ,瀏覽器強制執行互斥的折疊面板行為:
點擊卡片的摘要→ 卡片開啟(獲得[open]屬性),任何其他開啟的卡片都會自動關閉。
再次點擊同一摘要→ 它將關閉,返回預設風扇。
但通常情況下, <details>元素在關閉時會隱藏其內容。我們希望顏色卡片始終可見——開啟/關閉狀態應該只影響卡片的旋轉,而不影響其內容的可見性。這就是新的::details-content偽元素的作用所在:
details::details-content {
content-visibility: visible;
display: contents;
}
`::details-content偽元素指向 `<details> 的內容槽-即 <summary>之外的所有內容。透過將content-visibility設為visible並設定display: contents ,無論卡片處於開啟狀態,其顏色清單始終都會被渲染。
讓我們看看選擇一張卡片後的效果:

當一張牌被打開時,我們希望發生三件事:
目前卡片旋轉至 0°(垂直向上)
在它崩塌之前,牌組還在不斷改進。
之後的卡牌推動它走向終點
我們需要類似布林值的標誌位0或1供每張卡牌在其旋轉公式中使用。我們可以完全使用 CSS 選擇器來設定它們:
/* Any card is active */
section:has(details[open]) > * { --has-active: 1; }
/* Cards before the active one */
section > :has(~ details[open]) { --is-before: 1; }
/* The active card itself */
details[open] { --is-active: 1; }
/* Cards after the active one */
details[open] ~ * { --is-after: 1; }
四個選擇器,四個標誌。讓我們來逐一解讀:
section:has(details[open])符合任何 details 子專案打開時的部分,然後將--has-active: 1設為所有子專案。
:has(~ details[open])符合任何具有details[open]的後續兄弟元素-即,它位於活動卡之前。
details[open]直接符合目前已啟動的卡片。
details[open] ~ *符合所有後續兄弟卡-即目前卡片之後的卡片。
預設值均為0 ,設定在基礎section > *規則中。當沒有卡片開啟時,所有標誌均為0 ,卡片正常展開。
設定好標誌位後,旋轉公式可以處理所有狀態:
section > * {
rotate: calc(
(var(--start-degree) + (var(--end-degree) - var(--start-degree))
* (sibling-index() - 1) / (sibling-count() - 1))
* (1 - var(--is-active))
- var(--is-before) * (var(--end-degree) - var(--start-degree))
* (sibling-index() - 1) / (sibling-count() - 1) * 0.85
+ var(--is-after) * (var(--end-degree) - var(--start-degree))
* (1 - (sibling-index() - 1) / (sibling-count() - 1)) * 0.85
);
transition: rotate .25s linear;
}
到底發生了什麼事?
第 1-2 行:風扇正常旋轉-與先前的公式相同。
*` (1 - var(--is-active))` :**啟動時乘以 0 會使旋轉歸零 — 卡片會立即變成 0°。
卡牌產生前:減去一個值,使它們更靠近起始位置。 0.85 0.85係數會使它們緊密排列,但不會完全折疊。
在卡片之後:新增一個值,使它們進一步向末尾移動,使用相反的位置(1 - progress) ,使它們向相反的邊緣扇形展開。
這種transition賦予了它流暢、令人滿意的揮桿動作。
該元件依賴於幾個相對較新的 CSS 功能,因此請使用現代瀏覽器。
| 功能 | 它在這裡的作用 |
|---|---|
| progress() | 依容器寬度回傳 0-1,控制扇形展開 |
| sibling-index() | 每張卡片都知道它的位置-用於旋轉和 z-index |
| sibling-count() | 卡片總數 — 用於將位置標準化為 0-1 |
| <details name=""> | 獨家手風琴式折疊面板 — 點擊即可展開/收起,僅激活一個 |
| ::details-content | 覆蓋內容可見性,使卡片始終顯示其顏色 |
CSS 的發展速度和持續進步總是讓我驚嘆不已。最讓我興奮的是這些新功能是如何組合在一起的。它們單獨來看或許並不革命性,但progress()函數與sibling-index()驅動的旋轉效果相結合,再透過:has() 選擇器檢測到的原生<details> 狀態來切換,這一切都無需任何 JavaScript 程式碼。
這裡有一個 CodePen 演示。我強烈建議您全螢幕打開它,調整大小,點擊等等:
{% codepen https://codepen.io/stoumann/pen/zxByRmP %}
原文出處:https://dev.to/madsstoumann/re-creating-a-pantone-color-deck-in-css-3108