关于 liquid-metal-border
面向 React 的实现技能,指导将 `metal-fx` 动态边框用于语义化控件,并涵盖集成、无障碍、主题、动效、性能与验证。
主要功能
- 01在 React 18+ 中集成 `metal-fx`
- 02仅包裹一个语义化宿主元素
- 03配置状态驱动的边框与反射
- 04保留焦点、动效设置和 CSS 回退
- 05验证主题、性能与交互
适用场景
- 01为主要或选中按钮添加边框
- 02为邻近元素添加可选反射
- 03检查键盘焦点与减少动态效果
- 04排查渲染和 WebGL 回退问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mengto/ skills --skill liquid-metal-border安装后在 Claude Code 中输入/liquid-metal-border确认已加载。
附带文件agents/openai.yamlREFERENCES.md
详细介绍
液态金属边框
核心约定
- 在 React 18 或更高版本中使用
metal-fx。 - 恰好包裹一个真实的宿主元素,例如
button、a、div或article。 - 保持宿主元素语义清晰且可交互。
MetalFx负责视觉装饰,而不是充当控件。 - 仅在激活、选中、聚焦、悬停或主要界面上使用动画边框。不要给每个控件都加上边框。
- 保留静态 CSS 边框或焦点轮廓作为备用样式。WebGL 装饰绝不能单独承载必要状态。
- 暂停非激活实例,并为减少动态效果的用户暂停动画。
- 分别测试浅色和深色模式。反射效果仅在深色模式下呈现,这是有意设计的。
编辑前检查
- 确认项目使用 React,且
react和react-dom的版本为 18 或更高。 - 找到真实控件,并检查其尺寸、圆角、背景、边框、轮廓、阴影、激活状态来源和主题来源。
- 确定金属边框是始终显示,还是由
active、selected、悬停或聚焦状态驱动。 - 检查项目是否有手动主题切换。如果主题不会跟随操作系统,请使用该状态,而不是
theme="auto"。 - 确认附近哪些元素确实适合添加反射光效。默认不要添加反射效果。
- 使用下方的属性前,检查已安装的
metal-fx版本。
安装和导入
使用项目的包管理器安装:
npm install metal-fx
# pnpm add metal-fx
# yarn add metal-fx
# bun add metal-fx
导入具名 React 组件:
import { MetalFx } from "metal-fx";
不要导入样式表。此包会注入组件样式。它支持 ESM 和 CommonJS 构建,兼容 SSR,并在 hydration 后挂载 WebGL 流程。在 Next.js App Router 项目中,请从客户端组件渲染它,因为该组件使用客户端 React Hook。
基础按钮
先使用克制的边框,再添加反射效果或自定义着色器几何图形:
import { MetalFx } from "metal-fx";
export function UpgradeButton() {
return (
<MetalFx
variant="button"
preset="chromatic"
theme="auto"
strength={0.85}
>
<button type="button">Upgrade to Pro</button>
</MetalFx>
);
}
包装器会测量子元素,在其上方绘制金属环,并保持子元素可交互。使用 CSS、Tailwind 或内联样式为子元素设置常规尺寸。
激活状态模式
保持一个实例始终处于挂载状态,并根据真实的组件状态调整其强度。使用 aria-pressed、aria-selected 或原生选中状态机制,确保视觉效果不是唯一的状态提示。
"use client";
import { useEffect, useState } from "react";
import { MetalFx } from "metal-fx";
function useReducedMotion() {
const [reduced, setReduced] = useState(false);
useEffect(() => {
const query = window.matchMedia("(prefers-reduced-motion: reduce)");
const sync = () => setReduced(query.matches);
sync();
query.addEventListener("change", sync);
return () => query.removeEventListener("change", sync);
}, []);
return reduced;
}
export function LiquidMetalToggle({
selected,
onSelect,
}: {
selected: boolean;
onSelect: () => void;
}) {
const [hovered, setHovered] = useState(false);
const [focused, setFocused] = useState(false);
const reducedMotion = useReducedMotion();
const highlighted = selected || hovered || focused;
return (
<MetalFx
className="liquid-metal-toggle"
variant="button"
preset="chromatic"
strength={highlighted ? 0.9 : 0.14}
paused={reducedMotion || !highlighted}
disableGlow={reducedMotion || !highlighted}
ringCssPx={selected ? 1.5 : 1}
normalizeHostStyles={false}
>
<button
type="button"
className="liquid-metal-toggle__control"
aria-pressed={selected}
onClick={onSelect}
onPointerEnter={() => setHovered(true)}
onPointerLeave={() => setHovered(false)}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
>
Auto
</button>
</MetalFx>
);
}
.liquid-metal-toggle {
background: #18181b;
border-radius: 999px;
}
.liquid-metal-toggle__control {
min-height: 40px;
padding: 0 18px;
color: #fafafa;
background: transparent;
border: 0;
border-radius: 999px;
box-shadow: none;
}
.liquid-metal-toggle__control:focus-visible {
outline: 2px solid #ffffff;
outline-offset: 3px;
}
使用 paused 停止非激活画布的更新;它会冻结最后一帧,但不会隐藏边框。strength 会将渲染不透明度调整为 0 到 1,但不会降低着色器的运行速度。
静态边框和卡片模式
当子元素必须保持透明时,将包装器用作表面:
<MetalFx
variant="button"
preset="silver"
theme="dark"
strength={0.55}
borderRadius={24}
ringCssPx={1}
shaderScale={1.45}
disableGlow
normalizeHostStyles={false}
style={{ background: "#111318" }}
>
<article className="feature-card">
<h3>Realtime review</h3>
<p>Keep feedback attached to the work.</p>
</article>
</MetalFx>
保持 article 背景透明,以免遮住边框。使用子元素布局内容,并使用 MetalFx 包装器呈现可见表面。
完整的自定义选项
| 属性 | 取值和默认值 | 用途 |
|---|---|---|
children | 一个 React 宿主元素;必填 | 保留真实的按钮、链接、标签、卡片或图标控件。 |
variant | 默认值为 "button",也可设为 "circle" | 选择 shader 比例为 1.6 的 1 px 药丸样式基准,或比例为 1.3 的 2 px 紧凑圆形基准。渲染尺寸仍由测量到的子元素决定。 |
preset | 默认值为 "chromatic",也可设为 "silver"、"gold" | 选择彩虹色虹彩、冷调钢色或暖金色。每种预设都包含深色和浅色调校。 |
theme | 默认值为 "auto",也可设为 "dark"、"light" | 跟随实时的 prefers-color-scheme 变化,或将效果固定为应用主题。SSR 初始使用深色,随后在客户端解析。 |
strength | 数值 0..1;默认值为 1 | 调整 canvas 和光晕的不透明度。闲置状态使用较低值,活动状态使用较高值。 |
paused | 布尔值;默认值为 false | 将此实例冻结在当前 shader 帧,同时保持轮廓可见。 |
borderRadius | CSS 像素数值;可选 | 覆盖圆角半径。省略时,会在调整大小时读取子元素计算后的圆角半径。 |
normalizeHostStyles | 布尔值;默认值为 true | 移除子元素的背景、边框、轮廓和阴影,避免与环形效果冲突。若要保留自定义焦点轮廓或备用边框,请设为 false。 |
reflectionTargets | React 元素 ref 数组;可选 | 在选定的相邻元素上投射柔和的镜面反射。仅适用于深色模式。 |
disableGlow | 布尔值;默认值为 false | 移除游移光晕,同时保留 shader 环形效果。 |
shaderScale | 数值;默认使用对应变体的基准值 | 增大此值可放大金属图案特征;减小此值可缩小图案。 |
ringCssPx | 数值;默认使用对应变体的基准值 | 覆盖可见环形的 CSS 像素粗细。 |
scale | 数值;默认值为 1 | 同步缩放所有以绝对像素表示的引擎常量,以适配 CSS 缩放或有意放大的 UI 系统。 |
className | 字符串;可选 | 设置 MetalFx 包装器的样式,而非子元素的样式。 |
style | React CSS 属性;可选 | 直接调整包装器表面的尺寸或样式。 |
ref | React ref;可选 | 访问转发的包装器 HTMLDivElement。 |
其他有效的 HTMLDivElement 属性也会转发到包装器。
调校建议
- 主按钮:
variant="button"、strength={0.75}、ringCssPx={1}。 - 已选中的标签页或筛选项:闲置状态使用
0.1–0.2,选中状态使用0.75–0.9。 - 图标按钮:
variant="circle",为子元素明确设置正方形尺寸,并使用strength={0.7}。 - 大型卡片:
ringCssPx={1}、shaderScale={1.3–1.6}、disableGlow。 - 高级 CTA:从
chromatic开始;中性 UI 使用silver,仅在暖色或奢华配色中使用gold。 - 低调的活动状态:先禁用光晕,再考虑将环形效果减弱到难以辨认的程度。
- 双倍尺寸的设计系统:使用
scale={2},而不要分别将 shader、环形、光晕和反射的数值加倍。
常规控件的 strength 建议不超过 0.9。全强度的彩虹色金属效果可能会盖过标签和图标。
尺寸和圆角
优先调整子元素的尺寸:
<MetalFx variant="circle">
<button style={{ width: 36, height: 36 }} aria-label="Send">
↑
</button>
</MetalFx>
若要让框架大于子元素,请设置包装器的尺寸并拉伸子元素:
<MetalFx style={{ width: 44, height: 44 }} variant="circle">
<button
style={{ width: "100%", height: "100%" }}
aria-label="Send"
>
↑
</button>
</MetalFx>
包装器使用 display: inline-flex。不要创建循环的百分比尺寸,使包装器和子元素都没有固有尺寸。
邻近反射
仅传入相邻元素的明确 ref:
import { useRef } from "react";
const chipRef = useRef<HTMLButtonElement>(null);
<>
<button ref={chipRef}>Tools</button>
<MetalFx variant="circle" reflectionTargets={[chipRef]}>
<button aria-label="Send">↑</button>
</MetalFx>
</>
省略 reflectionTargets 即可禁用反射处理。在浅色模式下,反射会自动跳过。
无障碍与动态效果
- 在包装器内保留原生
button、a和表单语义。 - 将活动状态保存在
aria-pressed、aria-selected、选中状态或当前路由状态中。 - 不要只依赖色相或动画。保留文本、图标、位置、字重或静态边缘变化。
- 谨慎使用
normalizeHostStyles。其默认设置会移除子元素的outline;当控件需要自己的焦点环时,将其设为false,并定义透明外观以及:focus-visible样式。 - 为减少动态效果的场景设置
paused。当不需要光晕游移时,也应将其禁用。 - 保持效果不响应指针,并确认子元素仍可点击且可通过键盘操作。
- WebGL 失效时,保留普通的 CSS 边框或表面样式。
性能
metal-fx 会在已挂载的实例之间复用一个共享的 WebGL 上下文、一个已编译的着色器和一个动画循环。它还会通过 IntersectionObserver 暂停屏幕外的副本,并通过动画帧对调整大小的操作进行防抖。
仍应有选择地使用效果:
- 将其挂载在主要或具有状态的表面上,而不是整片闲置控件网格上。
- 暂停非活动实例。
- 只有在能增加可见景深时才添加反射效果。
- 不要持续动画化被包装元素的尺寸。
- 对于简洁的卡片和密集的工具栏,优先使用
disableGlow。
验证
运行项目的 lint、类型检查、生产构建、测试和 git diff --check。然后在 Codex 浏览器中验证:
- 确认清单和锁文件中包含该软件包。
- 确认一个
.metal-fx-root包裹一个语义宿主元素。 - 测试悬停、指针按下、选中、键盘焦点和禁用状态。
- 确认活动状态以语义方式呈现,并且在隐藏效果时仍清晰可辨。
- 确认启用或禁用
normalizeHostStyles时,焦点环均可见。 - 检查每个响应式断点下测得的尺寸和圆角。
- 测试应用实际的浅色和深色主题;不要假设
auto与手动切换的效果一致。 - 启用减少动态效果,并确认着色器停止移动。
- 将控件滚动至屏幕外再滚回;确认效果恢复时不会出现空白帧。
- 检查控制台输出中是否存在 WebGL、水合、调整大小或 ref 错误。
- 确认仅在深色模式下明确指定目标时,相邻控件才会出现反射效果。
故障诊断
- 圆环被遮住: 子元素在画布上方有不透明背景。使用默认规范化设置,或使子元素透明并为包装器设置样式。
- 焦点环消失:
normalizeHostStyles移除了子元素的轮廓。将其设为false,并且只在项目 CSS 中重置背景、边框和阴影。 - 圆角不正确: 在子元素上使用实际计算出的圆角,或显式传入
borderRadius。 - 圆形看起来模糊或过细: 使用
variant="circle",并先显式设置正方形尺寸,再增大ringCssPx。 - 图案过于繁杂: 降低
strength、禁用光晕,或使用silver;不要一开始就加粗圆环。 - 浅色模式下没有反射: 这是预期行为;反射仅适用于深色模式。
- 缩放后效果的实际尺寸不正确: 将
scale设为 UI 缩放比例,而不是分别调整每个像素常量。 - 页面在服务器组件中崩溃: 将用法移至框架的客户端组件边界之后。
- 不受支持的硬件上边框为空白: 保持语义控件和 CSS 回退完整;将 WebGL 圆环视为增强效果。
交接
报告已安装的 metal-fx 版本、被包装的控件、活动状态来源、预设、主题策略、回退边框、减少动态效果的行为、构建和测试结果,以及 Codex 浏览器验证情况。区分本地就绪状态与已部署结果。