🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

如果你曾經擁有過潘通色卡——那種平面設計師過去視若珍寶的色卡——你就會明白,這些卡片從一個鉚釘點向外展開時那種令人愉悅的感覺。每張卡片都沿著自己的弧線擺動,你可以用手翻閱它們。

我想在本週的 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()根據值在指定範圍內的位置傳回01之間的值。例如, progress(100cqi, 300px, 1440px)詢問:“容器的行內尺寸在 300px 和 1440px 之間有多大差距?”

  • 在 300px 或以下: --spread0 — 無扇形,卡片平放堆疊。

  • 在 1440px 或更高解析度下: --spread1 — 完全扇形,卡片跨度從 -45° 到 +45°。

  • 在 870px(中點)處: --spread0.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));
}

讓我們來詳細分析一下:

  1. sibling-index() - 1表示從零開始的索引位置(0 表示第一張卡片,1 表示第二張卡片,依此類推)。

  2. sibling-count() - 1表示卡片之間的「間隙」總數。

  3. 將它們相除,即可得到每張卡片位置的進度值,範圍從01

  4. 我們將該值乘以角度範圍,再加上起始偏移量。

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 ,無論卡片處於開啟狀態,其顏色清單始終都會被渲染。

讓我們看看選擇一張卡片後的效果:

選定顏色卡


僅基於 CSS 的狀態檢測

當一張牌被打開時,我們希望發生三件事:

  1. 目前卡片旋轉至 0°(垂直向上)

  2. 在它崩塌之前,牌組還在不斷改進。

  3. 之後的卡牌推動它走向終點

我們需要類似布林值的標誌位01供每張卡牌在其旋轉公式中使用。我們可以完全使用 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 新功能回顧

該元件依賴於幾個相對較新的 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


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

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝21   💬3  
560
🥈
我愛JS
📝1   💬5   ❤️2
66
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付