Liquid
Two-layer liquid effect group: an SVG silhouette carries the goo merge and real shadows while the content layer stays crisp and interactive.
Liquid
Basic Usage
Morph (default): give items x / y and the library animates the element and its liquid on one JS clock — touching pieces bridge and merge like droplets.
Move Trail
Move: you move the element however you like (CSS, pointer events) — the liquid chases it on a spring and drags a droplet tail.
Composition Examples
Shape Physics
morph.shape enables liquid shape-change physics: the mass flows toward the new centre first, then size and corner radius adapt — content cross-blurs while moving and sharpens as it settles.
<template>
<TxLiquid :blur="8" fill="var(--tx-bg-color)">
<TxLiquidItem :morph="{ shape: true, speed: 1, bounce: 0.5 }">
<div :class="open ? 'panel-open' : 'panel-closed'">…</div>
</TxLiquidItem>
</TxLiquid>
</template>
Dissolve
dissolve is a modifier orthogonal to effect: the item's imagery melts into a touching neighbour at the contact point through a turbulence displacement field — two liquids mixing, not a blur. Text is never melted.
<template>
<TxLiquid :blur="10" fill="var(--tx-bg-color)">
<TxLiquidItem dissolve>
<img class="avatar" src="/a.png" alt="">
</TxLiquidItem>
<TxLiquidItem :dissolve="{ mix: 0.7, active: dragging }">
<img class="avatar" src="/b.png" alt="">
</TxLiquidItem>
</TxLiquid>
</template>
Interaction Contract
TxLiquidrenders aposition: relative; isolation: isolatecontainer; the silhouette SVG sits atz-index: -1below every child, the melt overlay above the content layer, bothpointer-events: none.TxLiquidItemmust live inside aTxLiquidgroup, otherwise it throws.- Size the group to contain the items' full travel (like a menu reserving its open footprint); bridging happens roughly once
blur ≳ gap— raiseblurbefore suspecting anything else. - Corner radius is measured from computed style (percentages, pills and circles just work);
radiusoverrides it. - For
x/y/scale-driven items the component owns the wrappertransform; do not write your own transform or filter onto the wrapper. morph.shapewritesfilter: blur()onto the content mid-morph;dissolvewritesmask-imageonto<img>elements — don't combine either with your own.dissolvecombined witheffect="move"is ignored with a warning; component-driven transitions collapse to instant snaps underprefers-reduced-motion: reduce.
API
TxLiquid Props
| Prop | Type | Default | Description |
|---|---|---|---|
blur | number | 6 | Goo blur sigma (px) — how far apart pieces start bridging. |
contrast | number | 18 | Alpha-contrast slope; larger = sharper liquid edge. |
fill | string | '#fff' | Liquid surface color; var() welcome for theming. |
shadow | string | - | box-shadow syntax rendered on the MERGED silhouette; inset layers paint inside the liquid edge. |
filterPadding | number | 24 | Extra filter-region slack (px) for blobs travelling outside the group box. |
TxLiquidItem Props
| Prop | Type | Default | Description |
|---|---|---|---|
effect | 'morph' | 'move' | 'morph' | Liquid behavior. |
morph | MorphTuning | - | shape / speed / bounce / contentBlur plus the advanced escape hatch. |
move | MoveTuning | - | springiness / wobble / stretch / trail plus advanced. |
dissolve | boolean | number | DissolveOptions | - | Contact-melt modifier; 0..1 scales intensity. |
x / y / scale | number | 0 / 0 / 1 | Component-driven position, pixel-synced with the liquid. |
transition | 'snappy' | 'smooth' | 'bouncy' | SpringConfig | { duration, ease } | 'smooth' | Spring or duration transition for x/y. |
delay | number | 0 | Transition delay in ms (stagger). |
observe | boolean | false | Liquid follows the rendered rect of content you animate yourself; implied by morph.shape, dissolve and move. |
radius | number | [tl, tr, br, bl] | measured | Overrides the liquid corner radius. |
Slots
| Slot | Props | Description |
|---|---|---|
default (TxLiquid) | - | Group content, typically several TxLiquidItems. |
default (TxLiquidItem) | - | The real interactive content; keep its background transparent — the liquid is the surface. |
Events
No public events.
Exposed Methods
No public instance methods.
CSS Variables
No public CSS variables are consumed; pass fill="var(--surface)" for light/dark theming.
Best Practices
- Keep item backgrounds transparent — the blob is the surface; opaque content (a round photo) covers its own blob, which is exactly right for image chips.
- Pass a CSS variable as
fill(e.g.var(--tx-bg-color)) so the liquid follows the theme. - Put shadows on
shadowrather than on children, so one consistent shadow hugs the merged liquid through every merge and split. - When pieces that should merge look separate, tune the
blur-to-gap ratio before touching anything else. - Prefer the default
effect="move"feel for high-frequency dragging; reach foradvancedonly after the defaults prove insufficient.
Review Notes
- Manually verified against
index.ts,TxLiquid.vue,TxLiquidItem.vue,types.tsandliquid.test.tsunderpackages/tuffex/packages/components/src/liquid/. - The physics engine (
observer.ts,spring.ts,geometry.ts,shadow.ts) is a verbatim port of upstreamliquid-gooey(MIT © Jakub Antalik) with strict-TS index hardening only; the filter chain is rebuilt imperatively infilter-primitives.tsto avoid SVG namespace ambiguity. - Upstream React's
Liquid/Liquid.Itemmap toTxLiquid/TxLiquidItem;className/styleare covered by Vue attribute fallthrough.
Source
- Component source:
packages/tuffex/packages/components/src/liquid/src/TxLiquid.vue,TxLiquidItem.vue. - Types:
packages/tuffex/packages/components/src/liquid/src/types.ts. - Upstream: Jakubantalik/Libraries · liquid-gooey (MIT).
- Coverage:
packages/tuffex/packages/components/src/liquid/__tests__/liquid.test.tsverifies the silhouette/melt double layer, the goo filter chain, shadow splitting (drop-shadow vs SVG passes), morph blob mirroring, observed registration, and the outside-group throw.