MetalFx
WebGL2 liquid metal for buttons, text, and badges.
Usage
Basic
TxMetalFx wraps exactly one host element and paints a metal ring over it on a WebGL2 canvas.
Loading demo...
Variants
button is a pill with a 1 px ring and circle a true circle with a 2 px ring; the ring radius comes from the child's border-radius.
<template>
<TxMetalFx variant="button" preset="silver" :strength="0.7">
<a href="/pricing">Upgrade to Pro</a>
</TxMetalFx>
<TxMetalFx variant="circle" preset="gold" inner-shadow :scale="1.5">
<button type="button" aria-label="Send">↑</button>
</TxMetalFx>
</template>
Text and Badges
TxMetalText and TxMetalBadge always use the chromatic material and set aria-label to their own text.
<template>
<TxMetalText font="600 32px/1.1 Inter, sans-serif" color="#e8e8e8">Pro</TxMetalText>
<TxMetalBadge>Beta</TxMetalBadge>
</template>
Best Practices
- Give icon-only children an explicit width and height; the wrapper is
inline-flexand sizes to the child. - Keep the child's background transparent; put a custom fill on
TxMetalFxitself. - Use one preset and theme per page: every instance shares one material, and the last one applied wins.
- Keep metal elements apart; two metal buttons side by side compete for attention.
- Pass
reflectionTargetsonly for stable neighbours you can hold a reference to.
API Reference
TxMetalFx
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'button' | 'circle' | 'button' | Ring baseline: pill at 1 px, circle at 2 px. |
preset | 'chromatic' | 'silver' | 'gold' | 'chromatic' | Metal colour; each ships a dark and a light tuning. |
theme | 'auto' | 'dark' | 'light' | 'auto' | Picks the preset's dark or light side; auto follows the system live. |
strength | number | 1 | Scales shader opacity and glow alpha together (0-1). |
glowGain | number | 1 | Extra multiplier on the glow only, clamped to 0-1. |
paused | boolean | false | Freezes the instance on its current frame. |
borderRadius | number | auto-detected | Ring radius in CSS px. |
normalizeHostStyles | boolean | true | Strips the child's border, outline, and box-shadow. |
reflectionTargets | Array<Element | { ref, strength }> | none | Neighbours that catch a mirrored reflection. |
disableGlow | boolean | false | Removes the wandering halo; the ring still renders. |
innerShadow | boolean | { offsetY, blur, alpha, color } | off | Light rim along the ring's top inside edge. |
shaderScale | number | variant baseline × scale | Overrides the shader sampling scale. |
ringCssPx | number | variant baseline × scale | Overrides the ring thickness in CSS px. |
scale | number | 1 | Master multiplier on every absolute-pixel constant in the engine. |
mask | (ctx, size) => void | none | Custom alpha-mask painter; the shader shows only where it paints. |
glowMode | 'mask' | 'ring' | 'mask' | Glow placement when mask is set. |
Slots
| Slot | Props | Description |
|---|---|---|
default | - | Exactly one host element; it keeps its own event handlers. |
CSS Variables
| Variable | Source | Description |
|---|---|---|
--mfx-strength | strength | Strength clamped to 0-1, for downstream CSS to read. |
--mfx-radius | borderRadius or auto-detected | Ring radius in px. |
TxMetalText
Props
| Prop | Type | Default | Description |
|---|---|---|---|
font | string | required | CSS font shorthand. |
color | string | required | Base text colour behind the metal. |
strength | number | 1 | Metal opacity. |
reflectionTargets | Array<Element | { ref, strength }> | none | Neighbours that catch the metal. |
children | string | none | Text used when the default slot is empty. |
theme | 'auto' | 'dark' | 'light' | none | Picks the preset's dark or light side; follows the system when unset. |
innerShadow | TextInnerShadow | null | FIGMA_INNER_SHADOW | Light rim along the top inside edge of the glyphs; null turns it off. |
glow | boolean | false | Shows a halo on the glyphs. |
glowGain | number | 2.5 | Halo multiplier while glow is on. |
metalOpacity | number | 0.62 | How much metal shows over the base colour (0-1), multiplied by strength. |
shaderScale | number | 2.8 | Zoom of the metal inside the glyphs. |
Slots
| Slot | Props | Description |
|---|---|---|
default | - | The label text. |
TxMetalBadge
Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | string | 'New' | Text used when the default slot is empty. |
strength | number | 1 | Metal opacity. |
theme | 'auto' | 'dark' | 'light' | none | Picks the preset's dark or light side; follows the system when unset. |
scale | number | 1 | Size multiplier on the 45×25 base. |
reflectionTargets | Array<Element | { ref, strength }> | none | Neighbours that catch the metal. |
metalOpacity | number | 0.8 | How much metal shows over the white fill (0-1), multiplied by strength. |
shaderScale | number | 1.6 | Zoom of the metal texture. |
core | MetalBadgeCore | { r: 46, blur: 100, a: 0.94, size: 49 } | Radius, ramp, opacity, and size of the white core under the label. |
gradient | number | 0 | Strength of the top-to-bottom white wash (0-1). |
glow | number | 0.41 | Strength of the inner white glow (0-1). |
textColor | string | '#323232' | Label colour. |
Slots
| Slot | Props | Description |
|---|---|---|
default | - | The label text; falls back to children when empty. |
Overview
- All instances share one WebGL2 context, one material, and one loop at about 15 fps; offscreen instances (
IntersectionObserver, 64 px margin) are skipped, and the loop stops while the tab is hidden. - The wrapper, child included, stays invisible until the first metal frame paints; don't measure or animate the child before then.
- Reflections render only in dark theme; light theme does no DOM scan and no per-frame work.
- The shader ignores reduced motion; pass
paused(and optionallydisableGlow) yourself. - The cursor reflection needs a sprite registered through
setCursorSpriteand stays off without one; it also turns off under reduced motion,forced-colors, and coarse pointers. - Without WebGL2, the child keeps its own styles and renders inside
div.metal-fx-fallback[data-metal-fx-unsupported], with no ring.
Technologies
engine/**is a verbatim port of Jakubantalik/Libraries · metal-fx v2 (MIT © Jakub Antalik); the Vue shells mirror the React wrappers' lifecycle, measurement, and cleanup.- Page-level tuning uses the engine primitives exported from the module:
setGlowConfig,setCursorLightConfig,setBendConfig,isMetalFxSupported, and more. - Source:
packages/tuffex/packages/components/src/metal-fx/.
查看源码
packages/tuffex/packages/components/src/metal-fx/index.ts