useTween
useTween provides property tweening, number interpolation, and timeline sequencing for game animations. It reads from the TweenManager which is automatically set up by Game.
import { useTween } from "@carverjs/core/hooks";Quick Start #
import { useRef, useEffect } from "react";
import { Actor } from "@carverjs/core/components";
import { useTween } from "@carverjs/core/hooks";
import type { Group } from "@carverjs/core/types";
function BouncingBox() {
const ref = useRef<Group>(null);
const { tween } = useTween();
useEffect(() => {
if (!ref.current) return;
const controls = tween({
target: ref.current.position,
to: { y: 3 },
duration: 0.8,
ease: "bounce.out",
yoyo: true,
repeat: -1,
});
return () => { controls.kill(); };
}, [tween]);
return <Actor ref={ref} type="primitive" shape="box" color="orange" />;
}Return Value #
| Property | Type | Description |
|---|---|---|
tween(config) | <T extends object>(TweenConfig<T>) => TweenControls | Create and start a property tween |
tweenNumber(config) | (NumberTweenConfig) => TweenControls | Create a target-less number tween |
timeline(config?) | (TimelineConfig?) => TimelineControls | Create a timeline for sequenced animations |
killAll() | () => void | Kill all tweens created by this hook instance |
pauseAll() | () => void | Pause all active tweens |
resumeAll() | () => void | Resume all paused tweens |
Property Tweening #
Animate any numeric property on any object. Supports dot-notation for nested properties like position.x.
Basic #
const { tween } = useTween();
// Tween a Three.js position
tween({
target: ref.current.position,
to: { x: 5, y: 2 },
duration: 1,
ease: "quad.out",
});With Explicit Start Values #
tween({
target: ref.current.scale,
from: { x: 0, y: 0, z: 0 },
to: { x: 1, y: 1, z: 1 },
duration: 0.5,
ease: "back.out",
});Callbacks #
tween({
target: ref.current.position,
to: { x: 10 },
duration: 2,
onStart: () => console.log("started"),
onUpdate: (progress) => console.log("progress:", progress),
onComplete: () => console.log("done"),
});TweenConfig #
| Field | Type | Default | Description |
|---|---|---|---|
target | T extends object | — | Required. Object whose properties to animate |
to | Record<string, number> | — | Required. End values (supports dot-notation keys) |
from | Record<string, number> | — | Start values. If omitted, current values are captured |
duration | number | 0.3 | Duration in seconds |
ease | EasingInput | "quad.out" | Easing function or preset name |
delay | number | 0 | Delay before starting (seconds) |
repeat | number | 0 | Additional repeats. -1 for infinite |
repeatDelay | number | 0 | Delay between repeats (seconds) |
yoyo | boolean | false | Reverse direction on each repeat |
speed | number | 1 | Playback speed multiplier |
group | string | "" | Named group for batch control |
persist | boolean | false | Keep alive after completion (not auto-released to pool) |
ignorePause | boolean | false | Continue updating when game is paused |
onStart | () => void | — | Fired when the tween starts playing |
onUpdate | (progress: number) => void | — | Fired every frame with eased progress (0–1) |
onComplete | () => void | — | Fired when all repeats finish |
onRepeat | (count: number) => void | — | Fired after each repeat |
onYoyo | (direction: TweenDirection) => void | — | Fired when direction reverses |
onKill | () => void | — | Fired when the tween is killed |
TweenControls #
Every tween() call returns a TweenControls handle for imperative control:
const controls = tween({ target: obj, to: { x: 10 }, duration: 1 });
controls.pause(); // Pause
controls.resume(); // Resume
controls.kill(); // Stop and release
controls.restart(); // Replay from start
controls.seek(0.5); // Jump to 50% progress
controls.setSpeed(2); // Double speed
controls.chain({ ... }); // Chain another tween after completion| Property | Type | Description |
|---|---|---|
id | number | Unique tween ID |
state | TweenState | Current state: "playing", "completed", "killed", etc. |
progress | number | Current eased progress (0–1) |
pause() | () => TweenControls | Pause the tween |
resume() | () => TweenControls | Resume a paused tween |
kill() | () => TweenControls | Stop and release to pool |
restart() | () => TweenControls | Restart from the beginning |
seek(progress) | (number) => TweenControls | Seek to a progress value (0–1) |
setSpeed(multiplier) | (number) => TweenControls | Set playback speed |
chain(config) | (TweenConfig) => TweenControls | Chain a tween to play after completion |
finished | Promise<void> | Resolves when the tween completes or is killed |
All control methods return the same TweenControls for chaining:
tween({ target: pos, to: { x: 5 }, duration: 1 })
.chain({ target: pos, to: { y: 5 }, duration: 1 });Number Tweening #
Interpolate a single number without needing a target object. Useful for scores, timers, opacity, and any custom value.
const { tweenNumber } = useTween();
// Animate a score counter
tweenNumber({
from: 0,
to: 1000,
duration: 2,
ease: "expo.out",
onUpdate: (value, progress) => {
scoreElement.textContent = Math.round(value).toString();
},
});| Field | Type | Default | Description |
|---|---|---|---|
from | number | — | Required. Start value |
to | number | — | Required. End value |
duration | number | 0.3 | Duration in seconds |
ease | EasingInput | "quad.out" | Easing function |
onUpdate | (value: number, progress: number) => void | — | Called every frame with the current interpolated value |
All other fields are the same as TweenConfig (delay, repeat, yoyo, speed, etc.).
Timelines #
Sequence multiple tweens with precise timing control. Inspired by GSAP's timeline API.
Basic Sequence #
const { timeline } = useTween();
const tl = timeline()
.add({
target: ref.current.position,
to: { x: 5 },
duration: 1,
ease: "sine.inOut",
})
.add({
target: ref.current.position,
to: { y: 3 },
duration: 0.5,
});By default, each tween starts after the previous one finishes.
Overlapping Tweens #
timeline()
.add({ target: pos, to: { x: 5 }, duration: 1 })
.add({ target: pos, to: { y: 3 }, duration: 0.5 }, "-=0.3"); // overlap by 0.3sParallel Tweens #
timeline()
.add({ target: pos, to: { x: 5 }, duration: 1 })
.add({ target: scale, to: { x: 2, y: 2 }, duration: 1 }, "<"); // start at same timeLabels #
timeline()
.addLabel("intro")
.add({ target: pos, to: { x: 5 }, duration: 1 })
.addLabel("middle")
.add({ target: rot, to: { z: Math.PI }, duration: 0.5 }, "middle+=0.2");Callbacks #
timeline()
.add({ target: pos, to: { x: 5 }, duration: 1 })
.addCallback(() => console.log("halfway!"), "+=0")
.add({ target: pos, to: { y: 3 }, duration: 0.5 });Position Syntax #
| Syntax | Meaning |
|---|---|
| (omitted) | After previous entry ends |
0.5 | At absolute time 0.5s |
"+=0.5" | 0.5s after the previous entry ends |
"-=0.3" | 0.3s before the previous entry ends (overlap) |
"<" | At the same time the previous entry starts |
"<+=0.5" | 0.5s after the previous entry starts |
"label" | At the time of a named label |
"label+=0.3" | 0.3s after a named label |
TimelineControls #
| Property | Type | Description |
|---|---|---|
id | number | Unique timeline ID |
state | TweenState | Current state |
progress | number | Overall progress (0–1) |
duration | number | Total timeline duration in seconds |
add(config, position?) | Adds a tween at a position | Returns self for chaining |
addCallback(fn, position?) | Adds a callback at a position | Returns self for chaining |
addLabel(name, position?) | Adds a named label | Returns self for chaining |
play() | Start playing | Returns self |
pause() | Pause | Returns self |
resume() | Resume | Returns self |
kill() | Stop and clean up | Returns self |
restart() | Restart from beginning | Returns self |
seek(timeOrLabel) | Seek to a time (seconds) or label name | Returns self |
setSpeed(multiplier) | Set playback speed | Returns self |
finished | Promise<void> | Resolves on completion |
Easing Functions #
33 Robert Penner easing presets plus spring. Pass as a string to the ease option.
Presets #
| Preset | Typical Use |
|---|---|
"linear" | Constant speed (timers, progress bars) |
"quad.out" | Default. Smooth deceleration (UI slide-in) |
"quad.in" | Accelerating start (projectile launch) |
"cubic.inOut" | Smooth start/stop (camera pan) |
"quart.out" | Snappy deceleration (menu open) |
"sine.inOut" | Gentle oscillation (breathing effect) |
"expo.out" | Sharp snap to end (impact response) |
"elastic.out" | Spring overshoot (UI popup) |
"back.out" | Overshoot and settle (button press) |
"back.in" | Pull-back before action (jump windup) |
"bounce.out" | Bouncing ball (landing, drop) |
"spring" | Damped spring (natural bounce) |
Every equation comes in three variants: .in (accelerating), .out (decelerating), .inOut (both).
Custom Easing #
import { cubicBezier, steps } from "@carverjs/core/systems";
// CSS-compatible cubic bezier
tween({
target: pos,
to: { x: 10 },
ease: cubicBezier(0.68, -0.55, 0.27, 1.55),
duration: 1,
});
// Bezier as a tuple (shorthand)
tween({
target: pos,
to: { x: 10 },
ease: [0.68, -0.55, 0.27, 1.55],
duration: 1,
});
// Step easing (like CSS steps())
tween({
target: pos,
to: { x: 10 },
ease: steps(5),
duration: 1,
});
// Custom function
tween({
target: pos,
to: { x: 10 },
ease: (t) => t * t * t,
duration: 1,
});Repeat & Yoyo #
// Repeat 3 more times (4 total plays)
tween({ target: pos, to: { y: 5 }, repeat: 3 });
// Infinite repeat with yoyo (ping-pong)
tween({ target: pos, to: { y: 5 }, repeat: -1, yoyo: true });
// Delay between repeats
tween({ target: pos, to: { y: 5 }, repeat: -1, yoyo: true, repeatDelay: 0.5 });Chaining #
Chain tweens to play in sequence:
tween({
target: ref.current.position,
to: { x: 5 },
duration: 1,
}).chain({
target: ref.current.position,
to: { y: 3 },
duration: 0.5,
});Promise-based Completion #
The .finished property returns a Promise that resolves when the tween completes:
const controls = tween({ target: pos, to: { x: 10 }, duration: 1 });
await controls.finished;
console.log("tween done!");Pause-Immune Tweens #
Tweens pause automatically when the game phase is "paused" or "gameover". To keep a tween running during pause (e.g., pause menu animations):
tween({
target: menuRef.current.position,
to: { y: 0 },
duration: 0.3,
ignorePause: true,
});Cleanup #
All tweens created via useTween() are automatically killed when the component unmounts. No manual cleanup needed for typical usage. For explicit control:
const { tween, killAll } = useTween();
// Kill all tweens from this hook
killAll();
// Or kill individually
const controls = tween({ ... });
controls.kill();Game Phase Integration #
Tweens automatically respect the game phase:
| Phase | Behavior |
|---|---|
"loading" | All tweens frozen (including ignorePause: true) |
"playing" | All tweens update normally |
"paused" | Normal tweens freeze. ignorePause: true tweens continue |
"gameover" | Same as paused |
Type Definitions #
See Types for EasingFn, EasingPreset, EasingInput, TweenState, TweenDirection, TweenConfig, NumberTweenConfig, TweenControls, TweenGroupControls, TimelineConfig, TimelineControls, TimelinePosition, and UseTweenReturn.