verve.anim
Guide: Animation · live: example.
Builders
| Export | Notes |
|---|---|
to(alloc, target: ?selector) | tween toward given values |
from(alloc, target) | tween from given values to rendered state |
timeline(alloc) | sequence/compose tweens |
reveal(alloc, class_name, trigger_opts) | zero-wasm class toggle on scroll |
draggable(alloc, DraggableOpts) | pointer drag descriptor (Node.draggable) |
sortable(alloc, SortableOpts) | drag-to-reorder descriptor (Node.sortable) |
Tween chain
Properties: opacity x y scale scaleX scaleY rotate + propFrom(name, v) for arbitrary transforms. Timing: duration(s) delay(s) ease(Ease) repeat(n|-1) yoyo(bool). Keyframes: step(pct) + stepEase(Ease). Multi-target: stagger(.{ .each, .from }). Accessibility: reducedMotion(.respect|.skip). Specials: motionPath(.{ .path, .rotate }), morph(.{ .from, .to }), onComplete callbacks (island side).
Ease: linear, out_cubic, in_out_sine, out_back, elastic and bounce families, etc.
ScrollTrigger
.scrollTrigger(.{
.start = .{ .viewport = .{ .pct = 85 } }, // or .{ .trigger = .top, .viewport = .top }
.end = .{ .rel_vh = 1.5 }, // or absolute trigger/viewport specs
.actions = .{ .on_enter = .play, .on_leave_back = .reverse },
.scrub = .exact, // or .{ .smooth = 0.3 }
.pin = .self,
.snap = .{ .step = 1.0/3.0 }, // or .{ .points = &.{…} }
.snap_ease = .out_cubic, // any Ease; default .out_cubic
.snap_directional = false, // true = bias toward travel direction
.scroller = null, // CSS selector; wire "sl"
.scroller_handle = null, // island ref-handle; wire "slh"; wins over .scroller
.markers = true,
.once = true,
})scroller / scroller_handle: compute trigger geometry against a scrollable container instead of the window. v1: snap stays window-scoped; pin uses position:fixed — neither composes with container scrollers yet.
snap_ease: override the snap glide ease curve. snap_directional: when true, snaps to the nearest target in the direction of scroll travel (falls back to nearest if no target in that direction).
SplitText
Node.splitText(opts) — split a text node into animatable spans server-side.
| Option | Type | Default | Notes |
|---|---|---|---|
by | By | .chars | .chars · .graphemes · .words · .words_and_chars · .lines |
rtl_aware | bool | false | Wrap RTL runs in <span dir="rtl"> |
index_attr | bool | false | Stamp data-st-i on each leaf span |
Emitted classes: .st-char (chars/graphemes), .st-word (words), .st-line (lines — browser-grouped at hydrate). CSS requirement: .st-char,.st-word{display:inline-block}.
By.graphemes: keeps extended grapheme clusters whole (emoji families, skin-tone modifiers, regional-indicator flags, combining marks) — UAX#29, computed SSR in Zig. By.lines: emits word spans + data-split-lines; bridge groups by offsetTop once at hydrate.
A11y: the split-wrap is aria-hidden; the parent element receives aria-label with the original text.
Node hooks
Node.animate(t) · animateJson(json) · splitText(opts) (see above) · draggable(d) · sortable(s) · smoothScroll(.{ .smooth = 1.2 }) · parallaxSpeed(f) · parallaxLag(f).
Draggable opts
bounds (selector), inertia (.on), snap (.{ .grid = .{x,y} }), toggle_class, axis constraints, drop zones (on_drop island callbacks; .drop-hover class is zero-wasm).
bounce (?f64, default null) — elastic reflection when a throw hits a bound. Value in [0, 1]: 0 = clamp-equivalent; ~0.2 = GSAP-like; 1 = fully elastic. Requires both inertia (non-.off) and bounds (non-.none) — validation errors: BounceWithoutInertia / BounceWithoutBounds. Out-of-range values return BadBounce.
Sortable
Node.sortable(anim.sortable(alloc, opts)) — drag-to-reorder descriptor.
| Option | Type | Default | Notes |
|---|---|---|---|
items | []const u8 | — | CSS selector for sortable children (required) |
handle | ?[]const u8 | null | Grip sub-selector; null = whole item |
axis | Axis | .y | .x / .y / .both |
group | ?[]const u8 | null | Shared name for cross-list transfer |
animate | bool | true | FLIP sibling shift; wire "an":0 when false |
autoscroll | bool | true | Edge scroll; wire "as":0 when false |
autoscroll_edge_px | f64 | 40 | Edge band width in px; wire "ase" when not 40 |
toggle_class | ?[]const u8 | null | Class on dragged item |
disabled | bool | false | Start disabled; enable via verve.sortableEnable() |
Island-only callbacks (SSR rejects with CallbackSlotRequiresIsland):
| Callback | Fires | Requirement |
|---|---|---|
on_reorder_slot | After settle, with {from, to} | — |
on_enter_group_slot | When item enters any group container (both source and target) | group must be set |
on_enter_group_slot without group → GroupCallbackWithoutGroup.
SortableHandle (island-side, after callbacks fire):
const h: verve.SortableHandle = .{ .id = handle_id };
h.lastFrom() // slot index before move
h.lastTo() // slot index after move
h.fromContainer() // source container ref-handle; -1 if same list
h.toContainer() // target container ref-handle; -1 if same listValidation errors: SortableNoItems (empty items), GroupCallbackWithoutGroup (on_enter_group_slot without group), BadAutoscrollEdge (autoscroll_edge_px ≤ 0 or non-finite).
Wire root key "so". Respects prefers-reduced-motion (reorder instant, no FLIP). See sortable demo.
Island-side control
animPlay(timeline) + control exports (pause/play/reverse/restart, speed), flipCapture(selector) → DOM mutation → flipPlay(state,
opts, callbacks), drag handles with position/velocity reads, dynamic values + fn modifiers per frame, Observer (wheel/touch velocity), and scroll-trigger progress reads. The smoothed scroll position is readable end-to-end (verve_sm_get).