mcpField
L
liquid-metal-border

liquid-metal-border

github.com/mengto设计与 UI7.1k1.3k 次安装3 天前更新
代码仓库

关于 liquid-metal-border

面向 React 的实现技能,指导将 `metal-fx` 动态边框用于语义化控件,并涵盖集成、无障碍、主题、动效、性能与验证。

主要功能

  1. 01在 React 18+ 中集成 `metal-fx`
  2. 02仅包裹一个语义化宿主元素
  3. 03配置状态驱动的边框与反射
  4. 04保留焦点、动效设置和 CSS 回退
  5. 05验证主题、性能与交互

适用场景

  1. 01为主要或选中按钮添加边框
  2. 02为邻近元素添加可选反射
  3. 03检查键盘焦点与减少动态效果
  4. 04排查渲染和 WebGL 回退问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mengto/skills --skill liquid-metal-border

安装后在 Claude Code 中输入/liquid-metal-border确认已加载。

附带文件agents/openai.yamlREFERENCES.md

详细介绍

液态金属边框

核心约定

  1. 在 React 18 或更高版本中使用 metal-fx。
  2. 恰好包裹一个真实的宿主元素,例如 button、a、div 或 article。
  3. 保持宿主元素语义清晰且可交互。MetalFx 负责视觉装饰,而不是充当控件。
  4. 仅在激活、选中、聚焦、悬停或主要界面上使用动画边框。不要给每个控件都加上边框。
  5. 保留静态 CSS 边框或焦点轮廓作为备用样式。WebGL 装饰绝不能单独承载必要状态。
  6. 暂停非激活实例,并为减少动态效果的用户暂停动画。
  7. 分别测试浅色和深色模式。反射效果仅在深色模式下呈现,这是有意设计的。

编辑前检查

  • 确认项目使用 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 帧,同时保持轮廓可见。
borderRadiusCSS 像素数值;可选覆盖圆角半径。省略时,会在调整大小时读取子元素计算后的圆角半径。
normalizeHostStyles布尔值;默认值为 true移除子元素的背景、边框、轮廓和阴影,避免与环形效果冲突。若要保留自定义焦点轮廓或备用边框,请设为 false。
reflectionTargetsReact 元素 ref 数组;可选在选定的相邻元素上投射柔和的镜面反射。仅适用于深色模式。
disableGlow布尔值;默认值为 false移除游移光晕,同时保留 shader 环形效果。
shaderScale数值;默认使用对应变体的基准值增大此值可放大金属图案特征;减小此值可缩小图案。
ringCssPx数值;默认使用对应变体的基准值覆盖可见环形的 CSS 像素粗细。
scale数值;默认值为 1同步缩放所有以绝对像素表示的引擎常量,以适配 CSS 缩放或有意放大的 UI 系统。
className字符串;可选设置 MetalFx 包装器的样式,而非子元素的样式。
styleReact CSS 属性;可选直接调整包装器表面的尺寸或样式。
refReact 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 浏览器中验证:

  1. 确认清单和锁文件中包含该软件包。
  2. 确认一个 .metal-fx-root 包裹一个语义宿主元素。
  3. 测试悬停、指针按下、选中、键盘焦点和禁用状态。
  4. 确认活动状态以语义方式呈现,并且在隐藏效果时仍清晰可辨。
  5. 确认启用或禁用 normalizeHostStyles 时,焦点环均可见。
  6. 检查每个响应式断点下测得的尺寸和圆角。
  7. 测试应用实际的浅色和深色主题;不要假设 auto 与手动切换的效果一致。
  8. 启用减少动态效果,并确认着色器停止移动。
  9. 将控件滚动至屏幕外再滚回;确认效果恢复时不会出现空白帧。
  10. 检查控制台输出中是否存在 WebGL、水合、调整大小或 ref 错误。
  11. 确认仅在深色模式下明确指定目标时,相邻控件才会出现反射效果。

故障诊断

  • 圆环被遮住: 子元素在画布上方有不透明背景。使用默认规范化设置,或使子元素透明并为包装器设置样式。
  • 焦点环消失: normalizeHostStyles 移除了子元素的轮廓。将其设为 false,并且只在项目 CSS 中重置背景、边框和阴影。
  • 圆角不正确: 在子元素上使用实际计算出的圆角,或显式传入 borderRadius。
  • 圆形看起来模糊或过细: 使用 variant="circle",并先显式设置正方形尺寸,再增大 ringCssPx。
  • 图案过于繁杂: 降低 strength、禁用光晕,或使用 silver;不要一开始就加粗圆环。
  • 浅色模式下没有反射: 这是预期行为;反射仅适用于深色模式。
  • 缩放后效果的实际尺寸不正确: 将 scale 设为 UI 缩放比例,而不是分别调整每个像素常量。
  • 页面在服务器组件中崩溃: 将用法移至框架的客户端组件边界之后。
  • 不受支持的硬件上边框为空白: 保持语义控件和 CSS 回退完整;将 WebGL 圆环视为增强效果。

交接

报告已安装的 metal-fx 版本、被包装的控件、活动状态来源、预设、主题策略、回退边框、减少动态效果的行为、构建和测试结果,以及 Codex 浏览器验证情况。区分本地就绪状态与已部署结果。

评论

支持 Markdown