DroidGlass:適用於任何平台的 Liquid Glass
Liquid Glass 是 Apple 的一種材質,能夠折射其下方的內容。在邊緣附近,背景會像厚透鏡一樣向中心拉動,邊緣會出現柔和的彩虹色,一條極細的高光線沿著邊緣運行,觸碰時玻璃會膨脹、向手指方向拉伸,然後彈回。普通的玻璃態(glassmorphism)只會在白色薄霧下模糊背景,因此看起來廉價且無光澤。
品味的主要規則:**玻璃是透明的。**如果你無法閱讀其後方的內容,那就是 iOS 7 的磨砂玻璃,而不是 Liquid Glass。僅在文字位於玻璃之上時才添加模糊和表面效果。
誠實告知使用者限制所在。真正的材質僅存在於 iOS、iPadOS 和 macOS 26 及更新版本;其他一切皆為模仿。在 Android 上,Backdrop 提供了完整的模仿(透鏡效果需要 Android 13)。在 Web 上,折射僅在 Chromium(Chrome、Edge、Samsung Internet、Android WebView、Electron)中有效。Safari、Firefox 以及 iOS 上的所有瀏覽器會獲得帶有相同高光、陰影和彈簧效果的輕微模糊,但沒有彎曲效果。
工作流程
- 識別平台。
- SwiftUI、UIKit、AppKit、WidgetKit(iOS/macOS 26):系統 API,請參閱
references/apple.md。不要模仿任何東西。 - Jetpack Compose 或 Compose Multiplatform:
references/android-compose.md。不要從頭編寫著色器,使用函式庫。 - Web、Electron、Android WebView、Capacitor、Cordova、Tauri(Windows/Android):位於
assets/的引擎,請參閱references/web.md。在 WebView 和 Electron 中,折射始終有效。 - React Native、Flutter:沒有系統等效方案。對於 React Native,建議使用 WebView 層或基於 Backdrop 的原生模組;對於 Flutter,使用
BackdropFilter作為回退方案,並使用包含references/math-and-motion.md中公式的FragmentProgram。自行檢查當前套件,此技能不列出它們。
- SwiftUI、UIKit、AppKit、WidgetKit(iOS/macOS 26):系統 API,請參閱
- 選擇控制層的 1 到 3 個元素: 標籤列、浮動按鈕、頂部欄、表單、選單、切換開關或滑桿的拇指(觸碰時)。不要讓內容變成玻璃。
- 從下表選擇一個配方。 不要發明參數:這些數值來自 Kyant 的目錄並已在螢幕上驗證過。
- 檢查玻璃下方的背景。 玻璃僅在豐富的內容上可見:照片、顏色、圖表、滾動時的文字。在純白頁面上,透明玻璃幾乎會消失:在其後方放置顏色、添加一個遮罩層,或者移除玻璃。
- 根據正確的參考文件建構,並使用最後的檢查清單親眼驗證。沒有提供靜止、按壓和拖曳狀態的螢幕截圖之前,不要提交。
Apple 的規則
來自人機介面指南、WWDC25「Meet Liquid Glass」以及流行的社群技能。違反任何一條都會立即暴露其偽造性質。
- 僅用於浮動於內容之上的導航和控制層。 列表、行、卡片和頁面背景並非玻璃。例外:切換開關和滑桿的拇指在觸碰時會變成玻璃。
- 玻璃上不疊玻璃。 位於玻璃之上的元素透過填充和不透明度來區分。Backdrop 的例外:在玻璃表單上的滑桿拇指會折射該表單(
exportedBackdrop):一個透鏡疊在另一個之上,而不是兩個霧面平板。 - 少量使用。 僅用於最重要的控制項。
- 玻璃是無色的。 顏色和亮度來自背景:亮色背景上為亮色,暗色背景上為暗色,高光線條會根據背景色調進行著色。沒有自身的白色或灰色表面。
- 兩種模式,不要混用。 清晰模式(按鈕、拇指、放大鏡、與按鈕分組的標籤列):模糊度 0 到 4,無表面。常規模式(包含長文字的表單、對話框):模糊度 8 到 16;僅在文字在沒有表面時無法閱讀時,才添加 40% 到 60% 的表面效果。
- 僅對主要操作進行著色。
- 形狀。 膠囊形:半徑等於高度的一半。同心圓角:內圓角半徑等於容器半徑減去內邊距。
- 越大表示越厚: 較大的元素會獲得較寬的彎曲邊緣和較深的折射。
- 永遠不要讓內容一半留在玻璃下方。
- 液體運動。 按下時,玻璃會增長幾個像素,並以阻尼效果向手指方向傾斜;拇指會放大 1.5 倍,並以速度拉伸成水滴狀。所有動畫都使用彈簧效果,沒有線性過渡。
配方(數值來自 Kyant 的目錄)
| 元素 | 效果 | 高光與陰影 | 觸碰 |
|---|---|---|---|
| 按鈕,48 dp 膠囊形 | 活力感,模糊 2,透鏡(12, 24) | 預設,預設陰影 | 發光;高度增加 4 dp;偏移量 min × tanh(0.05 × offset / min);向手指方向拉伸最多 4 dp |
| 著色按鈕 | 同上 + 著色 | 同上 | 同上 |
| 帶表面按鈕 | 同上 + 白色 30% | 同上 | 同上 |
| 切換開關拇指 40×24,軌道 64×28 | 靜止時:白色 100%,模糊 8;按下時(p):模糊 8(1−p),透鏡(5p, 10p, 色散),白色 (1−p) | 環境光,寬度 /1.5,透明度 p;陰影半徑 4,5%;內部陰影半徑 4p,透明度 p | 縮放到 1.5;軌道透過透鏡可見,縮小到 0.75 |
| 滑桿拇指 40×24,軌道 6 dp | 類似切換開關,透鏡(10p, 14p) | 類似切換開關 | 縮放到 1.5;點擊軌道會跳轉到該點 |
| 標籤列 64 dp | 活力感,模糊 8,透鏡(24, 24),表面 #FAFAFA 40%(深色主題 #121212 40%) | 預設 | 按下時,整個面板寬度增加 16 dp(標籤隨之增加),並在手指之後最多傾斜 4 dp |
隱藏行副本 56 dp(tabsBackdrop) | 相同面板使用強調色,alpha(0):活力感,模糊 8,透鏡(24p, 24p),面板表面 | 預設透明度 p,InteractiveHighlight | 按下時圖標和標籤 ×1.2(LocalLiquidBottomTabScale 僅在此行設定) |
| 標籤列拇指 56 dp | 靜止時:灰色 10%;按下時:透鏡(10p, 14p, 色散) 作用於背景和行副本(rememberCombinedBackdrop(backdrop, tabsBackdrop)) | 預設,陰影和內部陰影 8p 透明度 p | 縮放 78/56;水滴內部有一個「欄中欄」:其自身項目為強調色並放大,邊緣可見相鄰項目 |
| 對話框,半徑 48 | 亮度 0.2(亮色主題),飽和度 1.5,模糊 16(深色主題 8),透鏡(24, 48, 深度) | 平面 | 使背景變暗 #29293A 23%(深色主題 #121212 56%) |
| 控制中心磁貼 | 活力感,透鏡(24, 48, 深度) | 預設,角度來自裝置傾斜,衰減 2 | 進入時:48 dp 偏移和透明度 |
| 放大鏡 128×96 | 透鏡(8, 24, 深度, 色散),背景縮放 1.5 倍 | 內部陰影 16 | 拖曳 |
彈簧、手勢以及每個平台共享的數學原理位於 references/math-and-motion.md。
一分鐘搞定 Web 與 Android WebView
將 assets/ 中的三個檔案原封不動地複製到專案中,並使用選項進行設定。不要重寫引擎。
<link rel="stylesheet" href="liquid-glass.css">
<button id="buy">購買</button>
<div id="wifi" aria-label="Wi-Fi"></div>
<div id="volume" style="width: 280px"></div>
<nav id="tabs" style="position: fixed; left: 16px; right: 16px; bottom: 16px"></nav>
<script src="liquid-glass.js"></script>
<script src="liquid-ui.js"></script>
<script>
LiquidUI.button(document.getElementById('buy'), { tint: '#0088FF', onClick: () => {} });
LiquidUI.toggle(document.getElementById('wifi'), { checked: true, onChange: (on) => {} });
LiquidUI.slider(document.getElementById('volume'), { value: 0.4, onInput: (v) => {}, label: '音量' });
const tabs = LiquidUI.tabs(document.getElementById('tabs'), {
items: [{ label: '首頁', icon: '<svg>…</svg>' }, { label: '搜尋', icon: '<svg>…</svg>' }],
index: 0, onChange: (i) => {},
});
// 融合:一個工具列和一個相鄰的按鈕會融合成一個水滴;相鄰的彈性按鈕會自動融合
// LiquidUI.blob([tabs.panel, fab], { blur: 3.5 }); LiquidUI.autoMerge(document.body); 螢幕切換後執行 LiquidUI.sweep()
// 為您自己的標記使用裸玻璃
const g = LiquidGlass.attach(document.getElementById('panel'), { radius: 28, blur: 8, refractionHeight: 24, refractionAmount: 24, surface: 'rgb(250 250 250 / .4)' });
// g.set({ lens: 0..1, blur, surface, highlightAlpha, angle, shadowAlpha, innerShadow, press: { p, x, y }, tx, ty, sx, sy, sleep })
</script>
| 檔案 | 說明 |
|---|---|
assets/liquid-glass.js | 玻璃引擎:折射貼圖、色散、高光、陰影、融合群組(LiquidGlass) |
assets/liquid-glass.css | 基礎樣式、無障礙回退、組件樣式 |
assets/liquid-ui.js | 彈簧、手勢和元件:按鈕、切換開關、滑桿、標籤列、放大鏡、blob、autoMerge(LiquidUI) |
assets/demo.html | 所有元件集中在一個頁面;打開它以查看正確效果 |
在進行任何更改或將其接入應用程式之前,請先閱讀 references/web.md:它列出了幾乎會破壞所有實作的 Chromium 相關知識(背景根元素、position: fixed、touch-action、body 背景)以及所有選項。當某些東西看起來不對勁時,請查閱 references/troubleshooting.md;當有任何卡頓問題時,請查閱 references/performance.md。
Web 上五個絕對不能搞錯的事項:
- 背景必須是真實的元素。設定在
body上並傳播到根元素的背景不會被backdrop-filter偵測到。 - 永遠不要在玻璃的上層元素上對
opacity、filter、mask或clip-path進行動畫處理(例如螢幕淡入):這會使它成為一個背景根元素,導致玻璃失去其背景並變暗。使用transform移動螢幕,並使用上層的遮罩層來顯示它們。 - 不要在玻璃元素上設定自己的
transform;請將基礎偏移量寫入--lg-base。 - 彈性按鈕需要
touch-action: none(預設值)。使用pan-y時,瀏覽器會劫持拖曳並發送pointercancel。 - 在 Android WebView 中,停用長按和觸覺回饋:
setOnLongClickListener(v -> true)和setHapticFeedbackEnabled(false)。
無障礙與易讀性
- Web 引擎會遵從
prefers-reduced-transparency: reduce(關閉玻璃效果,使用近乎不透明的表面;Chrome 和 Edge 119+,Safari 或 Firefox 尚不支援)、prefers-contrast: more(實心表面和邊框)以及prefers-reduced-motion(彈簧效果變成即時完成)。 - 元件會獲得角色和鍵盤支援:按鈕(Enter, Space)、
switch(Enter, Space)、slider(方向鍵)、tablist(左、右方向鍵)。使用aria-label或aria-labelledby為它們添加標籤。 - 在 Android 上,如 Kyant 的目錄所述,設定
semantics { role = Role.Switch }和標籤;在舊版 Android 上,檢查onDrawSurface顏色在沒有效果的情況下是否可讀。 - 檢查文字與最終可能位於面板下方的最亮、最雜亂的內容之間的對比度:主體文字需達到 4.5:1。如果失敗,請增加模糊和表面效果,或移動內容。文字陰影無法解決此問題。
禁止事項
- 在按鈕上使用白色半透明表面加上
blur(10px):那是磨砂玻璃,不是 Liquid Glass。只有包含文字的標籤列、表單和對話框可以適度使用表面密度。 - 玻璃內容卡片和玻璃頁面背景。
- 在大面板上使用色散效果:成本高且俗豔。
- 使用粗白色邊框代替極細高光線。
- 每個按鈕使用相同的著色。
- 使用線性
transition代替彈簧效果來處理按下和拖曳。 - 在 Web 上對玻璃的
width和height進行動畫處理:每幀都會重建貼圖。請對 scale 進行動畫處理。 - 在結果需要多個步驟讀取的情況下使用重濾鏡步驟(模糊、位移、卷積、形態學):Chrome 會為每個分支計算一次,導致手機卡死。
- 「為了美觀」而使用自己的表面繪製玻璃。只有包含文字的密集面板才能獲得表面效果。
- 在 Backdrop 適用時編寫自己的 Compose 著色器。
- 在 Apple 平台之外使用 SF Symbols 和 Apple 標誌:它們的授權禁止這樣做。請使用開源圖標集。
- 將 Web 或 Android 的結果稱為「官方 Liquid Glass」。它是風格上的模仿。
提交前的檢查清單
在 2 倍縮放下檢視螢幕截圖,並親自檢查:
- 在網格或文字上的靜止狀態: 線條可透過透明玻璃看到,在邊緣處向中心拉動,高光線條很細。在白色背景上,玻璃透過邊緣陰影和陰影與頁面區分開來;在深色背景上,透過亮面的一條亮線區分。
- 按下: 按鈕增大並向手指方向傾斜;拇指膨脹且透明,軌道可透過它看到,彩虹柔和,沒有彩色空洞。
- 拖曳: 拇指以速度拉伸,只有在到達後才縮小;標籤列傾斜並返回。邊緣標籤和靠近螢幕底部邊緣的標籤列水滴:其自身項目在內部,邊緣有相鄰項目,無黑色區域、條帶或鏡像碎片。
- 拉動: 按鈕像橡皮筋一樣跟隨手指並反彈回來;一個工具列向相鄰按鈕拉動時會與之融合,橋樑隨著它們靠近而增長,沒有接縫或雙重邊緣;在靜止和簡單按下時它們保持分離;正常點擊有效,將按鈕拉遠後鬆開不會觸發。
- 在照片上和白色背景上: 玻璃下的色彩豐富,文字可讀。
- 深色主題。
- 回退機制: 偽造引擎(Safari 使用者代理和空的
navigator.userAgentData),檢查元素是否沒有變成空洞,以及標籤列拇指下的圖標是否沒有模糊。 - 調整大小和旋轉: 貼圖被重建,邊緣沒有階梯狀瑕疵。
- 螢幕切換: 在螢幕進入時捕獲多個連續幀;玻璃按鈕絕不會比靜止時更暗,合併層在動畫後精確地位於其按鈕下方。
- 無控制台錯誤且無卡死:在一連串點擊後,頁面回應速度依然很快。一個動畫循環,沒有多餘的
requestAnimationFrame。 - 手機螢幕上不超過 3 個大型玻璃元素,沒有玻璃疊玻璃,沒有玻璃內容。
如果某項失敗,請先修復,然後再向使用者展示。
來源
- Kyant0/AndroidLiquidGlass, Backdrop library and catalog (Apache-2.0):透鏡、色散、高光和陰影公式、元件配方、彈簧和手勢常數。Web 引擎是此方案的移植。
- Apple:人機介面指南「材質」、「採用 Liquid Glass」、WWDC25「Meet Liquid Glass」。
- kube.io,「瀏覽器中的 Liquid Glass」:透過
backdrop-filter內部的feDisplacementMap實現折射。 - iyinchao/liquid-glass-studio (MIT):透過
smin融合形狀、Snell 折射、具有高對比玻璃的無色玻璃。