引言
苹果在 2025 年 6 月的 WWDC 上引入了 Liquid Glass 特效 —— 一种让界面元素看起来像由弯曲、折射玻璃制成的惊艳 UI 效果。本文将带你在 Web 上用 CSS、SVG displacement map 和基于物理的折射计算,重现类似的效果。
本文并不追求像素级还原,而是以核心的 折射 与 高光(specular highlight) 为目标,作为可扩展的实验性原型。
我们会从光线折射的物理原理讲起,再逐步搭建这个效果。
⚠️ 当前交互 demo 仅能在 Chrome 中运行(因为涉及 SVG filter 的 backdrop-filter)。 其他浏览器也能阅读文章并与部分内联模拟交互。
理解折射
折射是指光从一种介质进入另一种介质时改变方向的现象(例如空气进入玻璃)。这是因为光在不同介质中传播速度不同。
其规律由 Snell–Descartes 定律描述:
- :入射介质的折射率
- :入射角
- :折射介质的折射率
- :折射角
关键点:
- 当
$n_2 = n_1$,光线直线穿过,不发生折射。 - 当
$n_2 > n_1$,光线向法线方向偏折。 - 当
$n_2 < n_1$,光线远离法线偏折,且在一定条件下会发生 全反射(Total Internal Reflection)。 - 当入射光垂直入射时,不论折射率如何,光都会直线通过。
本项目的简化条件
为减少复杂性,本文限定如下:
- 环境介质为空气(index=1)。
- 使用折射率大于 1 的材料(典型值 1.5,即玻璃)。
- 只考虑一次折射事件(忽略出射时的二次折射)。
- 入射光始终垂直于背景平面(无透视)。
- 物体为与背景平行的二维形状。
- 无物体与背景的间隙。
- 本文只使用圆形,延伸到其他形状需额外计算。
在这些假设下,每条光线的折射方向都能通过 Snell’s Law 唯一确定。
构建玻璃表面
玻璃表面由一个函数定义,用于描述从边缘到中心的厚度变化。
const height = f(distanceFromSide);
入射角由曲面的切线(即函数的导数)决定:
const delta = 0.001;
const y1 = f(distanceFromSide - delta);
const y2 = f(distanceFromSide + delta);
const derivative = (y2 - y1) / (2 * delta);
const normal = { x: -derivative, y: 1 };
曲面函数示例
本文使用四种不同函数,展示曲面对折射的影响:
-
Convex Circle
简单圆弧 → 球面。转折较硬,折射边缘明显。
-
Convex Squircle
Apple 常用的 Squircle。过渡更平滑,折射渐变自然。
-
Concave
碗状凹面,折射光线向外发散。
-
Lip
外凸内凹,适合开关滑块效果。
位移向量场(Displacement Vector Field)
折射后的光线相对原位置的偏移,构成一个向量场。 在圆形中,偏移方向始终垂直于边界。
为了减少计算量,我们只需计算单个半径上的 127 个采样点,再旋转复制到整个圆形。
向量归一化与编码
SVG displacement map 需要每个像素的偏移量映射为颜色。
-
归一化:先计算最大位移,然后将所有向量缩放到 。
-
转换为颜色:
const x = Math.cos(angle) * magnitude; const y = Math.sin(angle) * magnitude; const result = { r: 128 + x * 127, g: 128 + y * 127, b: 128, a: 255 };
红色通道代表 X 轴偏移,绿色通道代表 Y 轴偏移。
SVG Displacement Map
SVG <feDisplacementMap /> 负责应用位移:
<svg colorInterpolationFilters="sRGB">
<filter id="liquidGlass">
<feImage href={displacementMapDataUrl} width={w} height={h} result="map"/>
<feDisplacementMap
in="SourceGraphic"
in2="map"
scale={maximumDisplacement}
xChannelSelector="R"
yChannelSelector="G"/>
</filter>
</svg>
scale参数决定了偏移强度,可动态调整实现动画。
高光(Specular Highlight)
真实玻璃有亮边。我们用一个 rim light 效果模拟,基于表面法线与光源方向的角度生成亮度。
最终效果通过 <feBlend /> 将高光与折射层叠加。
Chrome 限制
- Chrome 支持将 SVG filter 作为 backdrop-filter:
.glass-panel {
backdrop-filter: url(#liquidGlass);
}
- 其他浏览器不支持,只能退化为普通滤镜或模糊。
UI 组件示例
- 放大镜(Magnifying Glass):两层 displacement map,实现边缘折射+中心放大。
- 搜索框(Searchbox):Convex 玻璃框,折射背景。
- 开关(Switch):Lip 曲面,外凸内凹,中心缩小,边缘折射。
- 滑块(Slider):Convex 边框,背景随滑动折射。
- 音乐播放器(Music Player):模仿 Apple Music Liquid Glass 面板。
结论
本文原型将 Apple 的 Liquid Glass 效果简化为:
- 实时折射(refraction)
- 加 rim light 高光
该方法目前仅限 Chromium 内核(Chrome/Electron 等),其他环境可用模糊模拟替代。
但其可扩展性强,适合做 UI 探索。
Vue3 + Vite 的最简 demo,可以直接跑起来看到 “Liquid Glass 折射效果”。
因为浏览器限制,目前只能在 Chrome 中使用(依赖 backdrop-filter: url(#filterId))。
1. 新建项目
在终端运行(推荐用 pnpm/yarn 也可以):
npm create vite@latest liquid-glass-demo -- --template vue
cd liquid-glass-demo
npm install
2. 写一个 LiquidGlass.vue 组件
在 src/components/LiquidGlass.vue 中新建文件:
<template>
<div class="wrapper">
<!-- 背景 -->
<div class="background">
<div v-for="i in 40" :key="i" class="bg-line"></div>
</div>
<!-- 应用 Liquid Glass 滤镜的面板 -->
<div class="glass-panel">Hello Liquid Glass</div>
<!-- SVG Filter 定义 -->
<svg style="display: none">
<filter id="liquidGlassFilter">
<!-- 生成一个位移贴图(这里用简单噪声图代替预计算的 displacement map) -->
<feTurbulence type="turbulence" baseFrequency="0.05" numOctaves="2" result="turb" />
<feDisplacementMap
in="SourceGraphic"
in2="turb"
scale="20"
xChannelSelector="R"
yChannelSelector="G"
/>
</filter>
</svg>
</div>
</template>
<script setup>
// 没有 JS 逻辑,全部在 CSS + SVG 里
</script>
<style scoped>
.wrapper {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.background {
position: absolute;
inset: 0;
display: flex;
flex-wrap: wrap;
}
.bg-line {
width: 10%;
height: 10%;
background: linear-gradient(135deg, #ff7eb3, #65d6ce);
}
.glass-panel {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 20px;
font-weight: bold;
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.4);
/* 核心:使用 SVG filter 作为 backdrop-filter */
backdrop-filter: url(#liquidGlassFilter);
/* 背景半透明,配合玻璃质感 */
background: rgba(255, 255, 255, 0.15);
}
</style>
3. 在 App.vue 中使用
修改 src/App.vue:
<template>
<LiquidGlass />
</template>
<script setup>
import LiquidGlass from './components/LiquidGlass.vue'
</script>
4. 启动项目
npm run dev
打开 Chrome 浏览器,访问 http://localhost:5173/,就能看到一个半透明玻璃面板,带有液态折射效果。
⚠️ 注意:
- Safari / Firefox 不支持
backdrop-filter: url(#filter),只能看到普通半透明玻璃。 - 如果要实现文中提到的 基于 Snell’s Law 的真实 displacement map,需要在 JS 中生成图像并传给
<feImage />,这是下一步的增强。