Fuse Hold Button
- Skia
- Reanimated
- Gesture Handler
- Haptics
A destructive button you have to hold, with a lit fuse burning along its own outline. Letting go early does not reset it: the burn rewinds, so a slipped finger costs a moment rather than the whole hold.
The fuse is the button's own edge
The outline is built as a path whose corners are conics weighted 0.7071, which is what makes them true quarter circles. ContourMeasureIter then walks that path and getPosTan returns 65 points spaced evenly along its length, and those are where sparks are born. Even spacing matters: sampling by angle instead would crowd them into the corners.
const it = Skia.ContourMeasureIter(path, false, 1);const contour = it.next();const pts: SkPoint[] = [];if (contour) { const len = contour.length(); for (let i = 0; i < PERIM_N; i++) { // … const [pos] = contour.getPosTan((len * i) / (PERIM_N - 1)); pts.push(vec(pos.x, pos.y)); }}Letting go rewinds the burn
Release starts a 0.25 s rewind on a cubic ease, so the flame retreats fast and then eases to nothing. Grab again while it is retreating and the hold picks up from wherever it got to — not by keeping a second progress value, but by back-dating the start time so the burn stays one function of the clock.
// rewindLevel(p0, elapsedSec, rewindSec)const u = rewindSec <= 0 ? 1 : elapsedSec / rewindSec;const k = u >= 1 ? 1 : u <= 0 ? 0 : 1 - (1 - u) * (1 - u) * (1 - u);const v = p0 * (1 - k);// …holdStart.set(clock.get() - resume * holdMs);Forty sparks, allocated once
The pool is a fixed 40 and the density control only decides how many are alive; the rest are parked at a zero transform, which costs nothing to draw. Position, size and colour live in three Skia buffers rewritten each frame from the shared clock, and the whole pool goes out as a single Atlas call.
export const SPARK_POOL = 40;// …const transforms = useRSXformBuffer(SPARK_POOL, (val, i) => { 'worklet'; const s = sparkState.get(); if (state.get() !== ST_HOLDING || i >= live || pts.length < 2) { s.born[i] = -1; val.set(0, 0, 0, 0); return; }Tuning
- 1600
- #FFB020
- #FF6B35
- 24
- 0.6
- On
<FuseHoldButton params={{ holdMs: 1600, fuseColor: "#FFB020", glowColor: "#FF6B35", sparks: 24, heat: 0.6, shockwave: true }} fireSignal={0} />