@qyu/anim-react v1.0.1
@qyu/anim-react
React hooks for @qyu/anim-core to create and run declarative animations
Concept
- Create animation with useAnim* hook
- When Initial condition change - animation restarts, When target or config changes - animation updates without restarting
- Animations by the most part accept special type of input that is created with useInput* hooks. It allows to communicate updates to animation
- Pass created Animation to useRunAnim* hook
Animate Styles with springs
// create scheduler for browser, for native or node, use (Date, setTimeout, clearTimeout)
const scheduler_raf = fscheduler_new_frame(performance, requestAnimationFrame, cancelAnimationFrame)
const App = () => {
const tracker = useMemo(() => signal_new_value(0), [])
// animation will incrementally update when config changes
const [natfreq, natfreq_set] = useState(1e-2)
// animation will incrementally update when target changes
const [width_target, width_target_set] = useState(500)
const ref_element = useRef<HTMLDivElement | null>(null)
// run animation with animation frames
useRunAnimInterval({
// for fscheduler look at @qyu/anim-core
scheduler: scheduler_raf,
// will treat child animations individually as much as posiible
// that means when one of children changes it's initial value it will restart without affecting unrelated animations
// if false (by default) - runner will treat animation as monolith
spread: true,
src: useAnimStyleMapSpring({
// target element
ref: useRefObject(ref_element),
// default config for animations
config: useInputDynamicSet({
natfreq,
dampratio: 0.1,
}),
// useInputDynamicSet means
properties: useInputDynamicSet({
height: transvalue_new_cssunit({ from: 0, target: 400, unit: "px" }),
// when widht_target updates - this animation will also update
// you can override default config
width: transvalue_new_cssunit({ from: 0, target: { target: width_target, natfreq: 1e-3 }, unit: "px" }),
// color animation
// property names are not transformed = need to preserve dashes
// use tracker param to track value of something
// no animation on green param
"background-color": transvalue_new_csscolor([[0, 200], 150, [0, 255, { tracker }], [0.2, 0.8]]),
transform: transvalue_new_csstransform({
// no animation on this one
translateX: "100px",
// specific config for property
scaleX: transvalue_new_cssnumber({
from: 1,
target: {
target: 1.6,
natfreq: 5e-3,
precision: {
velocity: 1e-4,
displacement: 1e-5
}
},
})
})
})
}),
})
return <>
<button onClick={() => { natfreq_set(natfreq_old => natfreq_old * 2) } }>
Increase natfreq {natfreq}
</button>
<button onClick={() => { width_target_set(width_target_old => width_target_old + 500) } }>
Increase width_target {width_target}
</button>
<div ref={ref_element} />
</>
}Basic Linear animation
Animation will continuosly update on path conditions change (for linear animation it's config), but restart on initial conditions change. This works for all types of animations
const scheduler_raf = fscheduler_new_frame(performance, requestAnimationFrame, cancelAnimationFrame)
const App = () => {
const [target, target_set] = useState(100)
const [velocity, velocity_set] = useState(1e-1)
// run animation with animation frames
useRunAnimInterval({
scheduler: scheduler_raf,
src: useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target,
velocity,
effect: state => {
console.log("Animation Tick: ", state)
}
})
}),
})
return <>
<button onClick={() => { target_set(target_old => target_old + 100) } }>
Increase target {target}
</button>
<button onClick={() => { velocity_set(velocity_old => velocity_old * 2) } }>
Increase velocity {velocity}
</button>
</>
}useInput* hooks
Animations take special kind of input created with useInput hooks
// will never update no matter what
useInputConstant(10)
// will update when dependencies change
// dependencies can be ignored, then it will update when provided value changes
// as it updates will return new value and consequentially restart the animation
useInputStatic(10, [])
// accepts signal as parameter, when value in signal changes sends update to dependent animations
useInputDynamic(useSignalValue(10, [deps]))
// the same as previous one, just merged into one
useInputDynamicSet(10, [deps])usePath and useInit hooks
useAnim hook creates whole animation, but useInit and usePath* hooks can be used to only create point or path definition Almost all of animation variants have both useAnim, usePath and useInit variants
// does not request whole anim definition in links, see later
useAnimChain([
useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: 100,
velocity: 1e-2,
effect: state => {
console.log("Animation Tick: ", state)
}
})
}),
// only defining path
usePathLine(useInputDynamicSet({
target: 100,
velocity: 1e-2,
effect: state => {
console.log("Animation Tick: ", state)
}
}))
] as const)Creating Animation
Line
Linear animation
const App = () => {
const [target, target_set] = useState(100)
useRunAnimInterval({
scheduler: scheduler_raf,
src: useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target,
velocity: 1e-1,
effect: state => {
console.log("Animation 1: ", state)
}
})
})
})
return <button onClick={ () => { target_set(target_old => target_old + 100) } }>
Increase target {target}
</button>
}Spring
Spring-like animation
const App = () => {
const [target, target_set] = useState(100)
useRunAnimInterval({
scheduler: scheduler_raf,
src: useAnimSpring({
init: useInputConstant({
state: 0,
velocity: -100,
}),
config: useInputDynamicSet({
target: target,
// bigger faster
natfreq: 1e-3,
// dampratio < 1 - will overshoot, >= 1 will not overshoot, dampratio <= 0 - inifinite animation
dampratio: 0.1,
// will forcefully finish animation when it's too slow and close to target
precision: {
velocity: 1e-3,
displacement: 1e-4,
},
effect: state => {
console.log("Animation 1: ", state)
}
})
})
})
return <button onClick={ () => { target_set(target_old => target_old + 100) } }>
Increase target {target}
</button>
}Playback
Speed up or slow down animation
const App = () => {
const [target, target_set] = useState(100)
useRunAnimInterval({
scheduler: scheduler_raf,
src: useAnimPlayback({
config: useInputDynamicSet({
multiplire: 2.5
}),
src: useAnimSpring({
init: useInputConstant({
state: 0,
velocity: -100,
}),
config: useInputDynamicSet({
target: target,
natfreq: 1e-3,
dampratio: 0.1,
effect: state => {
console.log("Animation 1: ", state)
}
})
})
})
})
return <button onClick={ () => { target_set(target_old => target_old + 100) } }>
Increase target {target}
</button>
}Sequence
Emit animations in sequence, if animation completed once, when target updated it will emit in parallel
const App = () => {
const [target, target_set] = useState(100)
useRunAnimInterval({
scheduler: scheduler_raf,
src: useAnimSequence([
useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target,
velocity: 1e-1,
effect: state => {
console.log("Animation 1: ", state)
}
})
}),
useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target * 2,
velocity: 1e-1,
effect: state => {
console.log("Animation 2: ", state)
}
})
})
] as const)
})
return <button onClick={ () => { target_set(target_old => target_old + 100) } }>
Increase target {target}
</button>
}Sequence Strict
Init animations in sequence, but only emit one at a time event if it has finished before
const App = () => {
const [target, target_set] = useState(100)
useRunAnimInterval({
scheduler: scheduler_raf,
src: useAnimSequenceStrict([
useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target,
velocity: 1e-1,
effect: state => {
console.log("Animation 1: ", state)
}
})
}),
useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target * 2,
velocity: 1e-1,
effect: state => {
console.log("Animation 2: ", state)
}
})
})
] as const)
})
return <button onClick={ () => { target_set(target_old => target_old + 100) } }>
Increase target {target}
</button>
}Merge
Init animations in parallel
const App = () => {
const [target, target_set] = useState(100)
useRunAnimInterval({
scheduler: scheduler_raf,
src: useAnimMerge([
useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target,
velocity: 1e-1,
effect: state => {
console.log("Animation 1: ", state)
}
})
}),
useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target * 2,
velocity: 1e-1,
effect: state => {
console.log("Animation 2: ", state)
}
})
})
] as const)
})
return <button onClick={ () => { target_set(target_old => target_old + 100) } }>
Increase target {target}
</button>
}Chain
Emit animations in sequence, but with shared point
const App = () => {
const [target, target_set] = useState(100)
// first element should be animation definition
// chain links share point so no need to define init futher
useRunAnimInterval({
scheduler: scheduler_raf,
src: useAnimChain([
useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target,
velocity: 1e-1,
effect: state => {
console.log("Animation 1: ", state)
}
})
}),
usePathLine(useInputDynamicSet({
target: target * 2,
velocity: 1e-1,
effect: state => {
console.log("Animation 2: ", state)
}
}))
] as const)
})
return <button onClick={ () => { target_set(target_old => target_old + 100) } }>
Increase target {target}
</button>
}ChainMap
Like chain but with threads
const App = () => {
const [target, target_set] = useState(100)
// First element of each thread should be animation definition
// Thread share point, not need to define init futher
useRunAnimInterval({
scheduler: scheduler_raf,
src: useAnimChainMap([
{
thread_a: useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target,
velocity: 1e-1,
effect: state => {
console.log("Animation 1a: ", state)
}
})
})
},
{
thread_a: usePathLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target * 2,
velocity: 1e-1,
effect: state => {
console.log("Animation 2a: ", state)
}
})
}),
thread_b: useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target,
velocity: 1e-1,
effect: state => {
console.log("Animation 1b: ", state)
}
})
})
}
] as const)
})
return <button onClick={ () => { target_set(target_old => target_old + 100) } }>
Increase target {target}
</button>
}Loop
Repeat an animation
const App = () => {
const [target, target_set] = useState(100)
useRunAnimInterval({
scheduler: scheduler_raf,
src: useAnimLoop({
src: useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target,
velocity: 1e-1,
effect: state => {
console.log("Animation 1a: ", state)
}
})
}),
init: useInputConstant({
// will make it first time and then repeat 3 times
repeat: 3
})
})
})
return <button onClick={ () => { target_set(target_old => target_old + 100) } }>
Increase target {target}
</button>
}Merge Init and Path together
const App = () => {
const [target, target_set] = useState(100)
useRunAnimInterval({
scheduler: scheduler_raf,
src: useAnim(
useInitLine(useInputConstant({
state: 0
})),
usePathLine(useInputDynamicSet({
target: target,
velocity: 1e-1,
effect: state => {
console.log("Animation 1a: ", state)
}
})
),
})
return <button onClick={ () => { target_set(target_old => target_old + 100) } }>
Increase target {target}
</button>
}Pipe
Adapt animation for different kind of point
const App = () => {
const [target, target_set] = useState(100)
// first element should be animation definition
useRunAnimInterval({
scheduler: scheduler_raf,
src: useAnimChain([
useAnimPipe({
src: useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target,
velocity: 1e-1,
effect: state => {
console.log("Animation 1: ", state)
}
})
}),
pipei: (point_input: AnimSpring_Point): AnimLine_Point => ({
state: point_input.state
}),
pipeo: (point_input: AnimLine_Point): AnimSpring_Point => ({
state: point_input.state,
velocity: 1e-1
}),
}),
usePathSpring(useInputDynamicSet({
natfreq: 1e-2,
dampratio: 0.1,
target: target * 2,
effect: state => {
console.log("Animation 2: ", state)
}
}))
] as const)
})
return <button onClick={ () => { target_set(target_old => target_old + 100) } }>
Increase target {target}
</button>
}Cluster
Makes animation monolith, when one updates initial conditions - restarts the whole thing
const App = () => {
const [target, target_set] = useState(100)
useRunAnimInterval({
scheduler: scheduler_raf,
src: useAnimCluster(useAnimMerge([
useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target,
velocity: 1e-1,
effect: state => {
console.log("Animation 1: ", state)
}
})
}),
useAnimLine({
init: useInputConstant({
state: 0
}),
config: useInputDynamicSet({
target: target * 2,
velocity: 1e-1,
effect: state => {
console.log("Animation 2: ", state)
}
})
})
] as const))
})
return <button onClick={ () => { target_set(target_old => target_old + 100) } }>
Increase target {target}
</button>
}StyleMapSpring
Animate ref's styles with springs
const App = () => {
const [natfreq, natfreq_set] = useState(1e-2)
const [width_target, width_target_set] = useState(500)
const ref_element = useRef<HTMLDivElement | null>(null)
// run animation with animation frames
useRunAnimInterval({
scheduler: scheduler_raf,
// will treat child animations individually as much as posiible
// that means when one of children changes it's initial value it will restart without affecting unrelated animations
// if false (by default) - runner will treat animation as monolith
spread: true,
src: useAnimStyleMapSpring({
// target element
ref: useRefObject(ref_element),
// default config for animations
config: useInputDynamicSet({
natfreq,
dampratio: 0.1,
}),
// useInputDynamicSet means
properties: useInputDynamicSet({
height: transvalue_new_cssunit({ from: 0, target: 400, unit: "px" }),
// when widht_target updates - this animation will also update
width: transvalue_new_cssunit({ from: 0, target: width_target, unit: "px" }),
// color animation
// property names are not transformed = need to preserve dashes
"background-color": transvalue_new_csscolor([[0, 200], [0, 150], [0, 255], [120, 255]]),
transform: transvalue_new_csstransform({
// specific config for property
scaleX: transvalue_new_cssnumber({
from: 1,
target: {
target: 1.6,
natfreq: 5e-3,
precision: {
velocity: 1e-4,
displacement: 1e-5
}
},
})
})
})
}),
})
return <>
<button onClick={() => { natfreq_set(natfreq_old => natfreq_old * 2) } }>
Increase natfreq {natfreq}
</button>
<button onClick={() => { width_target_set(width_target_old => width_target_old + 500) } }>
Increase width_target {width_target}
</button>
<div ref={ref_element} />
</>
}StyleMapLine
Animate styles of target with line
const App = () => {
const [velocity, velocity_set] = useState(1e-2)
const [width_target, width_target_set] = useState(500)
const ref_element = useRef<HTMLDivElement | null>(null)
// run animation with animation frames
useRunAnimInterval({
scheduler: scheduler_raf,
// will treat child animations individually as much as posiible
// that means when one of children changes it's initial value it will restart without affecting unrelated animations
// if false (by default) - runner will treat animation as monolith
spread: true,
src: useAnimStyleMapLine({
// target element
ref: useRefObject(ref_element),
// default config for animations
config: useInputDynamicSet({
velocity
}),
// useInputDynamicSet means
properties: useInputDynamicSet({
height: transvalue_new_cssunit({ from: 0, target: 400, unit: "px" }),
// when widht_target updates - this animation will also update
width: transvalue_new_cssunit({ from: 0, target: width_target, unit: "px" }),
// color animation
// property names are not transformed = need to preserve dashes
"background-color": transvalue_new_csscolor([[0, 200], [0, 150], [0, 255], [120, 255]]),
transform: transvalue_new_csstransform({
// specific config for property
scaleX: transvalue_new_cssnumber({
from: 1,
target: {
target: 1.6,
velocity: 1e-4
},
})
})
})
}),
})
return <>
<button onClick={() => { velocity_set(velocity_old => velocity_old * 2) } }>
Increase velocity {velocity}
</button>
<button onClick={() => { width_target_set(width_target_old => width_target_old + 500) } }>
Increase width_target {width_target}
</button>
<div ref={ref_element} />
</>
}