Fluid Range Slider

  • Skia
  • Reanimated
  • Gesture Handler
  • Haptics

A two-thumb range where the chosen span is liquid. One SKSL pass over the track computes a waterline for every column of pixels, fills below it and shades by depth, so the surface keeps moving whether or not anyone is dragging.

A surface made of three waves

The waterline is a function of x. Three sines at 26, 13 and 47 cycles across the canvas run at different speeds, and the middle one travels the other way, so their sum never settles into a pattern the eye can follow. That sum is scaled by the track height and lifted off a resting level 22% down the track.

float rest = ty + th * 0.22;float k = x / max(u_res.x, 1.0);float wave =    sin(k * 26.0 + u_time * 2.1) * 0.10  + sin(k * 13.0 - u_time * 1.3) * 0.16  + sin(k * 47.0 + u_time * 3.4) * 0.05;float h = wave * th;

Grabbing lifts the liquid

Taking hold of a thumb raises the surface under it instead of flashing a highlight on it. The bulge is a gaussian in distance from the thumb, about 1.6 track heights wide, subtracted from the waterline along with the waves. Its strength decays on exp(-t / 0.6), so it drains away in about a second even if you keep holding.

float d = (x - u_rippleX) / max(th * 1.6, 1.0);float bump = exp(-d * d) * u_ripple * th * 0.9;return rest - h - bump;// …// rippleEnvelope(t, decay = 0.6)return Math.exp(-t / Math.max(decay, 1e-3));

Thumbs that cannot cross

Which thumb you get is decided once, on touch, by whichever is nearer, and it keeps the drag until you let go. Every proposed value is then clamped against the other thumb with a fixed gap held between them, so the low one stops short rather than passing through and silently swapping roles mid-gesture. Without that gap the two could land on the same value and the fill would vanish to nothing.

if (which === 'low') {  const lo = domainMin;  const hi = otherValue - gap;  return proposed < lo ? lo : proposed > hi ? hi : proposed;}const lo = otherValue + gap;const hi = domainMax;return proposed < lo ? lo : proposed > hi ? hi : proposed;

Tuning

  • #2D9CFF
  • 20
  • 80
  • On
<FluidRangeSlider params={{ fillColor: "#2D9CFF", min: 20, max: 80, bubble: true }} fireSignal={0} />