返回效果库

液态玻璃按钮

Liquid Glass Button

半透明、有厚度、会回弹的玻璃消息按钮

完整版:适合从零新建组件,规格、契约、验收全都在。

实时预览

指针交互
9:41

按住按钮 · 松手看果冻回弹

点击更换按钮底下的底纹

把指针移进这片区域触屏设备:直接在演示区域操作检测到「减少动态效果」,动效已降级

参数

边调边看,提示词同步更新。

角色

你是一名资深前端工程师,工作在现有的项目中,技术栈是 React 19 + TypeScript + Tailwind CSS v4 + Motion v14(motion 包)。只实现下面这一个微交互:不要加额外功能,不要顺手重构周围代码。

任务

实现一个浮在花纹壁纸上的液态玻璃消息按钮:10px 的背板模糊与 0.13 的白色着色让花纹透过玻璃仍然可见,1px 高光边包住按钮,下方 9px 的玻璃厚边提供真实的厚度。按下时它被压向厚边,松手后过冲回弹一次,像果冻一样(回弹 0.6)。

文件契约

- 只新建一个文件:components/effects/LiquidGlassButton.tsx。

- 依赖:项目已安装 motion@14,从 motion/react 引入。不要新增任何其他包。

- 不要修改 package.json、全局 CSS、Tailwind 配置或任何其他已有文件。

实现要点

- 文件顶部加 "use client"。

- 用 useMotionValue(0) + animate()(来自 motion/react)驱动按下量,按下的弹簧与松手的弹簧参数不同,松手时保留过冲。

- 用 useTransform 从按下量派生 scaleX、scaleY 和玻璃面层的 y;指针位置只写 CSS 变量 --mx / --my,不要走 React state。

- 玻璃面层用行内样式写 backdropFilter 与 WebkitBackdropFilter(模糊值来自 props),并加 overflow-hidden + border-radius: inherit,否则模糊会溢出圆角。

- 导出 LiquidGlassButton,props 为 blur、tint、thickness、bounce、highlight、title、subtitle、onClick、className。

- 用 useReducedMotion() 降级:不做形变与高光跟随,按钮仍可点击、仍有焦点环。

规格 —— 精确数值,不许四舍五入或“优化”

- 按钮:292px × 64px(最大宽度 100%),圆角 22px,内边距 0 14px,背景透明,按钮元素本身不加边框。

- 玻璃面层:绝对定位 inset 0,继承圆角,overflow: hidden,边框 1px 实线 rgba(255, 255, 255, 0.42);backdrop-filter: blur(10px) saturate(1.7) brightness(0.97),同时写 -webkit-backdrop-filter。

- 模糊层之上的着色:linear-gradient(155deg, rgba(255,255,255,0.13) 0%, rgba(255,255,255,0.059) 52%, rgba(255,255,255,0.098) 100%)。面层必须保持半透明,底下的花纹要能辨认。

- 内侧高光:inset 0 1px 0 rgba(255, 255, 255, 0.62), inset 0 -1px 1px rgba(255,255,255,0.22), inset 0 -12px 20px -14px rgba(255,255,255,0.35)。

- 厚度边:面层后面再加一层——左右各 3px,top 100%,高 15px,margin-top -6px,圆角 0 0 22px 22px;背景 linear-gradient(180deg, rgba(255,255,255,0.34) 0%, rgba(255,255,255,0.10) 32%, rgba(9,16,28,0.55) 100%);1px 实线 rgba(255,255,255,0.18);backdrop-filter: blur(10px) saturate(1.4) brightness(0.8);box-shadow: 0 14px 26px rgba(0,0,0,0.26)。

- 顶部高光:inset 0 的绝对定位层,linear-gradient(180deg, rgba(255,255,255,0.35) 0%, rgba(255,255,255,0.08) 34%, rgba(255,255,255,0) 58%),透明度 0.6,被面层裁切。

- 按下形变:按钮 scaleX = 1 + 0.05p、scaleY = 1 - 0.14p,transform-origin 50% 70%;玻璃面层和内容向下位移 0.55 × 9 × p px,厚度边保持不动,所以可见厚度被压薄。p 是 0→1 的按下量。

- 弹簧:按下用刚度 640、阻尼 34、质量 0.7;松手用刚度 460、阻尼 22.8、质量 0.7。松手必须过冲一次再回到静止。

- 悬停:整个按钮上浮 1.5px、缩放到 1.015;悬停过程中不产生回弹。

- 指针高光:radial-gradient(150px circle at var(--mx) var(--my), rgba(255, 255, 255, 0.55), transparent 62%),mix-blend-mode: screen,静止时透明度 0,指针进入后最高 0.9。在按钮上写 --mx / --my 百分比来跟随指针。

- 内容:图标块 38px 见方、圆角 12px、1px 实线 rgba(255,255,255,0.35)、半透明白渐变;右上角一个 8px 的 #3DDC84 未读点(2px 半透明白色描边);标题 13.5px / 600 / 1.15,字距 -0.01em,白色;副标题 10.5px,颜色 rgba(255, 255, 255, 0.78);两者文字阴影 0 1px 3px rgba(8,10,18,0.45);右侧 14px 箭头,70% 白。

- 只允许绘制 transform、opacity 与 backdrop-filter;组件里任何一层都不能不透明到遮住壁纸。

行为

- 组件本身就应该是透明的:它直接放在宿主页面背景上,并让背景透过玻璃被看见。

- pointerdown(仅主键):按下弹簧朝 1 走。pointerup、pointercancel、pointerleave 都只释放一次回到 0;重复释放不能堆积动画。

- 键盘:空格 keydown 按下、keyup 释放,不要阻止原生 click;Enter 走原生 click;必须是真实的 <button type="button">,并保留可见的 focus-visible 描边。

- 触屏:同样的压扁与回弹;高光跟随触点,但不做悬停上浮。

- 指针在按钮内移动时更新 --mx / --my 与高光;离开时高光淡出、上浮归零。

- prefers-reduced-motion: reduce:不压扁、不上浮、不追踪高光;按钮保持静止但仍能触发 click。

- 卸载时清理动画与监听(不要有 document / window 级监听);不要在卸载后更新状态。

禁止

- 不要在按钮或玻璃面层上写不透明背景、实色渐变终点或 background: white。

- 不要用 canvas、SVG 滤镜或复制一张壁纸来伪造模糊;必须用 backdrop-filter。

- 不要动画 width / height / margin / padding / top / left;厚度变化只能来自面层的位移。

- 不要在组件外面额外做壁纸、底纹选择器、演示壳或页面。

- 不要留 TODO、不要写 // 其余代码、不要用 any、不要 console.log、不要留注释掉的实验代码。

- 不要改动全局 CSS、Tailwind 配置或上面文件之外的任何文件。

- 除 Motion 外不要引入任何动画库,更不要用 GSAP。

验收 —— 回答前逐项自查

1. 透过玻璃能清楚看到底纹:切换四款底纹时,按钮内部的颜色与纹理随之改变,没有不透明填充。

2. 圆角外没有模糊溢出或光晕;1px 高光边完整包住玻璃面。

3. 按钮下方有一条可见的玻璃厚边;按下时它被压薄,松手后恢复原厚度。

4. 按下是快速的压扁,松手只过冲一次并在约 0.6s 内稳定,没有持续抖动。

5. 文字的对比度在四款底纹上都足够;按钮在触屏、鼠标和键盘下都可点击。

6. prefers-reduced-motion 下不播放形变与高光,按钮仍保留焦点环并可触发 click。

7. 零控制台报错;卸载后监听与动画都被清理;只改变 transform / opacity / 背板滤镜。

输出

- 用一个代码块输出完整文件内容,不要 diff、不要省略号,代码块之前不要解释。

- 如果缺少必要依赖,先停下来问我,不要擅自安装。

- 代码块之后最后输出一行:self-check: 1 ✓ · 2 ✓ · 3 ✓ · 4 ✓ · 5 ✓ · 6 ✓ · 7 ✓,未通过的项用 ✗ 并附一句原因。

与演示同源生成3,953 字符 · 1,098 约 tokens · 64 行
技术规格

复刻 iOS 消息通知上的液态玻璃:一层毛玻璃让底下的团花纹仍然透出来,1px 高光边圈住按钮,下方 9px 的玻璃厚边把它托起来;按下时被压扁,松手后过冲回弹,像一块果冻。演示舞台提供四款底纹,点击即可更换按钮下面的背景。

背板模糊
10 px
玻璃着色
0.13
玻璃厚度
9 px
果冻回弹
0.6
指针高光
开启
按钮
292 × 64 px · 22px
背板滤镜
blur(10px) · saturate(1.7)
玻璃厚边
9 px
按下形变
1.05 × 0.86 · y +55%
回落弹簧
460 / 22.8
高光半径
150px · 0.9
验收清单

提示词要求 AI 在输出末尾逐条自检。

  1. 透过玻璃能清楚看到底纹:切换四款底纹时,按钮内部的颜色与纹理随之改变,没有不透明填充。
  2. 圆角外没有模糊溢出或光晕;1px 高光边完整包住玻璃面。
  3. 按钮下方有一条可见的玻璃厚边;按下时它被压薄,松手后恢复原厚度。
  4. 按下是快速的压扁,松手只过冲一次并在约 0.6s 内稳定,没有持续抖动。
  5. 文字的对比度在四款底纹上都足够;按钮在触屏、鼠标和键盘下都可点击。
  6. prefers-reduced-motion 下不播放形变与高光,按钮仍保留焦点环并可触发 click。
  7. 零控制台报错;卸载后监听与动画都被清理;只改变 transform / opacity / 背板滤镜。

待回归验证提示词与演示已同源生成;尚未在真实 Cursor / Claude Code 项目中做回归。

提示词如何还原这个效果

不是把效果描写一遍,而是把效果写成一份可执行的工程需求。

数字规格
每个像素、每个时间参数都来自同一个参数源——滑块改什么,提示词就写什么。
文件契约
只允许新建指定文件、指定依赖白名单,避免它顺手改你的全局样式或装一堆包。
验收清单
提示词末尾强制 AI 自检布局位移、键盘可用、reduced-motion 等 7 项。
完整版 / 中文

React + Tailwind + Motion