Components/Liquid

Liquid

Two-layer liquid effect group: an SVG silhouette carries the goo merge and real shadows while the content layer stays crisp and interactive.

VerifiedSince 0.3.9

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.

Loading demo...

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.

Loading demo...

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

  • TxLiquid renders a position: relative; isolation: isolate container; the silhouette SVG sits at z-index: -1 below every child, the melt overlay above the content layer, both pointer-events: none.
  • TxLiquidItem must live inside a TxLiquid group, 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 — raise blur before suspecting anything else.
  • Corner radius is measured from computed style (percentages, pills and circles just work); radius overrides it.
  • For x/y/scale-driven items the component owns the wrapper transform; do not write your own transform or filter onto the wrapper.
  • morph.shape writes filter: blur() onto the content mid-morph; dissolve writes mask-image onto <img> elements — don't combine either with your own.
  • dissolve combined with effect="move" is ignored with a warning; component-driven transitions collapse to instant snaps under prefers-reduced-motion: reduce.

API

TxLiquid Props

PropTypeDefaultDescription
blurnumber6Goo blur sigma (px) — how far apart pieces start bridging.
contrastnumber18Alpha-contrast slope; larger = sharper liquid edge.
fillstring'#fff'Liquid surface color; var() welcome for theming.
shadowstring-box-shadow syntax rendered on the MERGED silhouette; inset layers paint inside the liquid edge.
filterPaddingnumber24Extra filter-region slack (px) for blobs travelling outside the group box.

TxLiquidItem Props

PropTypeDefaultDescription
effect'morph' | 'move''morph'Liquid behavior.
morphMorphTuning-shape / speed / bounce / contentBlur plus the advanced escape hatch.
moveMoveTuning-springiness / wobble / stretch / trail plus advanced.
dissolveboolean | number | DissolveOptions-Contact-melt modifier; 0..1 scales intensity.
x / y / scalenumber0 / 0 / 1Component-driven position, pixel-synced with the liquid.
transition'snappy' | 'smooth' | 'bouncy' | SpringConfig | { duration, ease }'smooth'Spring or duration transition for x/y.
delaynumber0Transition delay in ms (stagger).
observebooleanfalseLiquid follows the rendered rect of content you animate yourself; implied by morph.shape, dissolve and move.
radiusnumber | [tl, tr, br, bl]measuredOverrides the liquid corner radius.

Slots

SlotPropsDescription
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 shadow rather 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 for advanced only after the defaults prove insufficient.

Review Notes

  • Manually verified against index.ts, TxLiquid.vue, TxLiquidItem.vue, types.ts and liquid.test.ts under packages/tuffex/packages/components/src/liquid/.
  • The physics engine (observer.ts, spring.ts, geometry.ts, shadow.ts) is a verbatim port of upstream liquid-gooey (MIT © Jakub Antalik) with strict-TS index hardening only; the filter chain is rebuilt imperatively in filter-primitives.ts to avoid SVG namespace ambiguity.
  • Upstream React's Liquid / Liquid.Item map to TxLiquid / TxLiquidItem; className / style are 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.ts verifies 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.
查看源码
packages/tuffex/packages/components/src/liquid/index.ts