毛玻璃工藝
網路上的多數玻璃看起來廉價,原因有三:背後沒有有趣的內容、材質僅為單一色調加單一模糊、以及上方的文字是未經測量的混濁灰色。看起來高級(Apple 的材質、visionOS、頂尖產品網站)的玻璃,會依序正確處理三件事:
- 玻璃後方的光線經過設計。 玻璃會展示後方的內容,因此需在後方放置光源。
- 玻璃是一種材料,而非色調。 飽和模糊、漸層填充、發光邊框、鏡面光澤、深度、紋理。請參考
assets/glass.css。 - 文字經過測量。 半透明玻璃會因背後每個像素而改變對比度,因此需在三個狀態下,透過腳本在最差位置檢查對比度(
scripts/glass-contrast.mjs)。
快速路徑
| 我正在建構… | 使用 | 備註 |
|---|---|---|
| 黏性導航欄 | .gs-bar(surfaces.css) | 延伸背景;加上 gs-bar--on-scroll 僅在滾動後才呈現玻璃質感 |
| 播放器、Dock 或覆蓋於藝術圖上的藥丸形按鈕 | .gs-dock.glass.glass--clear + liquid-glass.js | 清晰的液態玻璃,每個畫面一個;若藝術圖變化大則使用自適應色調 |
| 含文字的浮動卡片或面板 | .glass.glass--regular | 在細節照片上使用 thick |
| 選單、彈出視窗或指令列 | .gs-menu.glass | 32px 模糊於 ~86% 填充,0.5px 頂部高光 |
| 對話框 | .gs-veil + .gs-dialog.glass.glass--thick | 確認金錢、刪除或簽署時使用 glass--solid;玻璃鍍鉻層在下方時使用 gs-veil--flat + .gs-modal-open |
| 提示或底部工作表 | .gs-toast.glass.glass--regular / .gs-sheet.glass.glass--thick | gs- 類別僅負責佈局;材質來自 .glass |
| 內容在橫條下方淡出 | 漸進模糊(配方 2) | 每個視圖一個滾動邊緣效果 |
| 標題出現或數值變化 | 模糊揭示(4)/ 2px 交叉淡入淡出(4b) | 每個畫面一次 |
| 頁面被藝術圖顏色浸染 | 藝術圖背景,烘焙(配方 8) | 固定玻璃下絕不使用即時模糊 |
| Tailwind v4 專案 | glass.css + glass.tailwind.css(glass-blur-24,…) | Tailwind 自身的 backdrop-blur-* 在 Sonoma Safari 上會失效 |
| 原生應用(SwiftUI、Flutter、React Native、Compose) | references/native.md | 使用相同方法,各工具包的材質 |
| 照片、影片或設計編輯器的鍍鉻層 | .glass.glass--thick.glass--neutral | 中性:無飽和度提升,玻璃不得重新著色作品 |
接著:構圖光線(§1)、設定材質(§2)、檢查(§5)。assets/demo.html 以即時控制項展示每種表面;在選擇前開啟該檔案了解每個類別的作用。
1. 先構圖光線
決定每個玻璃表面正後方是什麼,並使其成為該處最明亮的元素。
-
在玻璃下方放置光源: 光暈、藝術圖中的太陽、產品的顏色、即時 WebGL 元件。模糊的平坦顏色仍是相同的平坦顏色;虛空上方的玻璃只是個灰色盒子。
-
讓光線越過邊緣。 最好的玻璃一半位於光線上方,一半位於暗處,因此顏色會在其寬度上產生變化。
-
將邊框朝向光源。 將
--glass-light設定為光源的顏色(例如夕陽的255 200 160),並將--glass-rim-angle設定為其方向。邊框的亮端朝向光源:光源方向 左上 上 右上 右 右下 下 左下 左 角度 135deg 180deg 225deg 270deg 315deg 0deg 45deg 90deg 在玻璃下方的光源,其角度取其最近邊緣的角度加上約 15deg 朝向中心。此角度是已註冊的
@property,因此可平滑地跟隨指標或滾動(Apple 的高光會隨著光線移動而在材質周圍移動)。 -
將玻璃塑造成其應有的英雄尺寸。 當玻璃夠大,能顯示其下的場景變化時,它就讀起來像一種材料。Apple 讓較大的玻璃表現得像更厚的玻璃:更深的陰影、更柔和的光散射。
2. 設定材質
使用 assets/glass.css(複製時重新命名 glass 前綴)和 assets/surfaces.css 以取得現成表面,或使用元件:Glass.tsx(React)、Glass.vue(Vue 3)、Glass.svelte(Svelte 5)。請勿手寫一行式的 backdrop-filter 卡片。
使用文字值書寫 backdrop-filter,絕不使用 var(),不帶前綴的寫在前面,-webkit- 寫在最後。 macOS Sonoma 上的 Safari 18 和 26 在使用自訂屬性構建 filter 時會渲染不出模糊(WebKit 297620,未解決),而 Tailwind v4 的 backdrop-blur-* 正是以這種方式構建。同一份錯誤報告顯示,即使使用文字值,不帶前綴的屬性在那裡也會失敗,因此帶前綴的宣告放在最後,Safari 會應用它(Chrome 和 Firefox 會忽略它)。glass.css 對每個厚度 × 色調組合都有文字值的 filter,並透過類別切換;變數適用於填充、尺寸和顏色。
厚度。 根據玻璃承載的文字量來選擇。這些值是透過在 demo.html 中對明亮光源使用對比度檢查器校準的:
| 類別 | 模糊、變暗 | 在明亮光源上通過 4.5:1? | 用途 |
|---|---|---|---|
glass--clear | 16px, 22% | 否:僅限粗體或大字型文字及圖示 | Dock、播放按鈕、媒體上的藥丸形按鈕(Apple:清晰需要粗體、明亮的內容和變暗圖層) |
glass--thin | 24px, 30% | 在最亮光源上不通過 | 控制項、標籤、中等背景上的小卡片 |
glass--regular | 32px, 48% | 在校準頁面上通過;播放器後方的明亮夕陽需要 thick 搭配 0.62 | 播放器、面板、提示:預設值。模糊關閉(僅色調)狀態通常是首先失敗的 |
glass--thick | 44px, 56% | 通過 | 選單、工作表、對話框,以及任何文字繁重的內容 |
glass--solid | 4px, 92% | 通過 | 付款、刪除、簽名(填充約 85% 以上時,大模糊無法被看見,因此使用 4px) |
對於淺色玻璃搭配墨水色文字,加上 glass--light;在色彩關鍵的應用(照片、影片和設計編輯器)中使用 glass--neutral:無飽和度提升或色調偏移,因為玻璃不得改變作品的觀感。在細節照片上,從 thick 搭配 --glass-dim: rgb(10 10 18 / 0.65–0.75) 開始(這是顏色,不是數字):鮮豔的玻璃和可讀的文字在此相互拉扯,因此將鮮豔度用在邊框、光澤和玻璃周圍溢散的光線上。
兩層級,如同 iOS 26.1: 一個色調層級(透明度更高,絕不更低)在 prefers-contrast: more(Safari 用戶可達的設定;Safari 沒有 prefers-reduced-transparency)時啟用,透過 glass--tinted,或透過 <html data-glass="tinted"> 全站啟用。glass.css 也處理 Windows 高對比度(實色 Canvas 表面)、列印(純邊框盒子)、螢幕擷取工具的捕捉模式,以及 RTL。
玻璃屬於控制層(導航欄、工具列、Dock、選單、工作表、對話框),浮動於內容之上,絕非內容本身,也絕非玻璃疊玻璃(Apple,WWDC25)。僅對主要操作進行色調處理。符合簡報的風格:沉浸式簡報在主要控制項上採用強光與清晰玻璃;可信賴的簡報(金融、健康)採用厚或實心玻璃及極少光線。
references/material.md 包含其餘資訊:每個圖層的作用及原因、從 Apple.com、Linear、Raycast、Radix、Material 和 Fluent 讀取的預設、Apple 完整的 WWDC25 規則列表,以及風格表。
3. 標誌性效果
references/recipes.md 中的程式碼:
- 液態玻璃折射: 邊框像透鏡一樣彎曲後方的內容,可選色差(0.6–1)。兩個版本:
liquid-glass.ts/.js可折射任何頁面內容,但僅限於 Chromium(Safari 對 SVG 背景 filter 不繪製任何東西,WebKit 245510);liquid-glass-webgl.js可折射頁面自身繪製的背景(一個<canvas>、<img>或<video>,例如英雄壁紙),在每個瀏覽器中都有效,已在 Chromium 和 WebKit 中驗證渲染。在其他地方,霜凍材料本身必須看起來完整。折射和霜凍之間需要取捨:彎曲透過清晰玻璃顯示(blur0–6);霜凍(12–20)從邊緣到邊緣都霜凍,彎曲變成了柔和的色彩偏移。清晰適用於單詞或圖示,霜凍適用於句子,文字在邊緣內側一個邊框寬度處。絕不用於遮罩上的對話框:那裡沒有尖銳的東西可以彎曲。 - 黏性橫條的延伸背景:顏色在內容滾入玻璃下方之前就已到達。
gs-bar--on-scroll使用純 CSS(滾動驅動動畫)讓橫條僅在滾動後才呈現玻璃質感。 - 漸進模糊: 內容在橫條下方溶解,而非被切斷。在 CSS 獲得
linear-blur()之前(已提議,csswg #13285),疊加遮罩圖層是唯一方法。 - 模糊橋接交叉淡入淡出: 在狀態交換期間使用 2px 模糊(Emil Kowalski)。
- 模糊揭示: 從
blur(8px)開始,僅一次,作為出現的瞬間。 - 焦點遮罩: 對話框後方的頁面後退 4–8px。
- 玻璃變形與按壓: 在相鄰玻璃形狀之間的
layoutId變形(Cursor:0.34 秒,[0.32, 0.72, 0, 1]),以及按壓時的內部光暈脈衝,而非縮放。 - 合併玻璃形狀: 一個由 goo 濾波器 SVG 遮罩切割的玻璃圖層,使相鄰形狀像 Apple 的玻璃容器一樣以液態頸部連接(配方 11,已在 Chromium 中測試)。
- 來自影片的環境光暈: 一個 10×6 像素的畫布,對正在播放的影片進行取樣並放大(配方 8b)。
- 色調處理: 結構性玻璃的色調約 0.03–0.05,色調使用相對顏色 alpha(配方 12)。
- 景深、速度動態模糊、藝術圖背景(烘焙)、鎖定內容、紋理。
4. 保持快速
先設計外觀,然後遵守以下規則:
- 長時間或循環動畫應使用 opacity 和 transform,而非模糊半徑。短暫的一次性動畫沒問題:Apple.com 將其選單簾幕的模糊 0 → 20px 動畫設為 320ms。
- 絕不同時模糊兩個疊加的表面(Apple 的導航欄在彈出視窗開啟時會放棄自身的模糊)。當對話框在玻璃鍍鉻層上方開啟時,將
.gs-modal-open添加到<html>(鍍鉻層停止模糊並變為接近不透明),並使用gs-veil--flat,或從圖像或畫布背景烘焙遮罩。在照片編輯器測試中測量:五個玻璃面板上的一個即時全螢幕遮罩模糊運行在 31fps;沒有它時,60fps。 - 固定或黏性模糊每個滾動幀都會重新執行。每個畫面一個大的;在其下方烘焙大的模糊圖像(測量:即時模糊從 60 → 22fps,烘焙後回到 60)。
- 手機:七個 18px 面板在 Mali-G57 上每幀花費約 3.5ms(75–80% 的幀按時完成,而沒有模糊時為 99%)。如果面板超過幾個,請在佈局的電話斷點處放棄
backdrop-filter並保留半透明填充(或讓 glass-tier.js 根據設備決定)。 - 預算模糊面積,而非半徑或數量。 此技能測量結果(Chromium,軟體渲染 2×,移動背景,vsync 開啟):在相同總面積下,半徑 4 → 64px 和 1 → 9 個表面沒有差異;模糊 90% 的螢幕降至 51fps(p95 33ms)。這與 Skia 對大面積模糊進行降採樣的情況相符。數值是相對的(一台快速 Mac 在軟體中渲染),排名是可轉移的。
- 為
html提供一個不透明的背景色:Safari 隨後會為帶有玻璃的頁面跳過一個額外的全螢幕合成圖層(WebKit 271340:此修復僅對不透明文件跳過該圖層)。 - 不要在不透明填充下進行模糊。Framer 和 Dia 在實色背景上使用
backdrop-filter,為了一個無人能見的模糊而付出代價。填充約 85% 以上時,使用 ≤ 4px 或不使用(Arc 的提示:85% 和 3px)。 - 根據設備對玻璃進行分級:
assets/glass-tier.js根據設備記憶體、Save-Data、幀率探測(iOS 低耗電模式會刻意將動畫限制在 30fps)和 Compute Pressure,將data-glass-tier設為 full、lite(一個 8px 模糊)或 flat(無模糊,更多色調),如同 Android、Fluent 和 Apple 在弱電或節能設備上放棄模糊的方式。 - 在影片上方,不要模糊正在播放的影片:將其取樣到一個小畫布中(配方 8b),並在全螢幕模式下放棄玻璃。Safari 永遠不會將影片像素傳遞給 SVG filter。
- 背景根:一個父級元素,其
opacity < 1、filter、遮罩、clip-path、mix-blend-mode、backdrop-filter或will-change應用於其中任何一個,都會使內部的玻璃扁平化。這包括 Framer Motion 父級淡入淡出和玻璃水滴父級上的 goofilter。應淡入淡出玻璃本身;在完成時清除揭示 filter(filter: none)。 backdrop-filter會使元素成為position: fixed子元素的包含塊:入口選單、提示和對話框應脫離玻璃容器。- 祖先上持續存在的
view-transition-name會在 Chromium 中使玻璃扁平化;僅在轉換期間設定名稱。包裝器上的框架淡入淡出轉換也會產生相同效果:應淡入淡出玻璃本身。 - 對帶有玻璃的頁面進行截圖、OG 圖像或 PDF 生成時:html2canvas、Satori 和列印會丟失或破壞模糊。在擷取期間將
data-glass-capture設於<html>(glass.css 會使玻璃變不透明),並參閱 quirks.md。
5. 檢查(必要)
在將玻璃視為完成之前,運行以下所有檢查:
- 霜凍檢查:
node <skill>/scripts/frost-check.mjs <url or file.html>測量每個玻璃表面模糊移除了多少細節(模糊開啟 vs 關閉)。NO BLUR 表示存在背景根、前綴或var()問題,或液態玻璃映射過時;FLAT 表示背後沒有可測量的內容。若要以肉眼檢查,請將粗體紅色文字放在玻璃後方,在deviceScaleFactor: 1下擷取並放大。 - 對比度,在最差背景上測量:
node <skill>/scripts/glass-contrast.mjs <url or file.html>。將其指向最明亮的現實狀態,而非漂亮的預設狀態:最亮的照片、白天的幀、曝光 +2 EV 的圖像、滾動到橫條下方的白色頁面部分(添加 URL 參數或測試狀態,以便腳本可以載入)。在某個編輯器上,預設照片以 8.7:1 通過,而一個明亮的幀以 4.4:1 失敗。也要檢查手機寬度(--width 390 --height 844)。它會隱藏文字,找出每行下方最亮和最暗的像素,並在三個狀態下檢查 4.5:1(3:1 用於大字型):正常、僅色調(模糊關閉,如同 iOS 低耗電模式顯示的狀態)和prefers-contrast: more。透過將厚度提高一級、提高--glass-dim中的 alpha 值來修復失敗(例如
…
[翻譯時已截斷]