Skip to content

Animations

SymbioteNative ships a from-scratch, framework-agnostic port of React Native’s Animated API. The value graph, easing curves, interpolation, and composition helpers (timing, spring, decay, parallel, sequence, stagger, loop, delay) live once in @symbiote-native/engine — every adapter re-exports the same Animated namespace and the same createAnimatedComponent mechanism, so Animated.Value, Animated.spring, and the rest behave identically no matter which framework renders them.

import { Animated } from '@symbiote-native/react';
const pulse = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.loop(
Animated.timing(pulse, { toValue: 1, duration: 1400, useNativeDriver: true }),
).start();
}, []);
const scale = pulse.interpolate({ inputRange: [0, 0.5, 1], outputRange: [1, 1.3, 1] });
<Animated.View style={{ transform: [{ scale }] }} />;

Why not Svelte’s transition:/animate:/in:/out:?

Section titled “Why not Svelte’s transition:/animate:/in:/out:?”

Those directives — along with use:, class:, and style: — are Svelte’s own element-only directives: the compiler itself rejects them on a component tag (“This type of directive is not valid on components”), the same as it would for any third-party Svelte component library, not just SymbioteNative’s. Every SymbioteNative primitive (View, Text, …) is a component reference in app code, never a raw DOM element, so <View transition:fade> simply doesn’t compile — this is Svelte’s own component/element split, not an adapter limitation. {@attach fn} is the one directive in that family that is legal on a component, and it’s your route to the underlying host node when you need it; for animation itself, reach for the same Animated API and $effect-driven lifecycle shown above.

useNativeDriver: true hands the entire animation curve to a native-side driver (NativeAnimated) — once started, zero JS runs per frame, and the animation keeps running even if the JS thread is busy. useNativeDriver: false (the default) runs the same math in JS and commits a new value through the engine on every frame — necessary for properties the native driver can’t touch (most non-transform/opacity style properties), at the cost of a commit per frame.

Both drivers use the exact same Animated.Value/Animated.timing call — useNativeDriver is the only thing that changes. See examples/react/App.tsx’s AnimatedDemo for a JS-driven dot and a native-driven dot running side by side, with DEBUG=1 logging showing the difference directly (a native run logs one native: startAnimatingNode; a JS run logs a commit … incremental roughly once per frame).

The full RN Animated surface ported, not just timing:

  • Animated.ValueXY — a 2D value pair, typically paired with PanResponder for drag gestures.
  • Animated.spring/Animated.decay — physics-based animations, same config shape as RN’s.
  • Tracking — passing another Animated.Value (or ValueXY) as toValue makes the animation chase a moving target instead of a fixed number, the same “tracking” mechanism RN uses for gesture-follow effects.
  • add/subtract/multiply/divide/modulo/diffClamp — operators that combine animated values, e.g. a collapsing header driven by diffClamp(scrollY, 0, HEADER_HEIGHT).
  • Animated.event(...) — maps a native event’s payload directly onto an Animated.Value with no JS in the loop (onScroll={Animated.event(...)}).
  • Animated.parallel/sequence/stagger/loop/delay — the same composition helpers RN ships, unchanged.

examples/react/App.tsx’s AnimatedParityDemo exercises all of this in one place: a ValueXY-driven drag box clamped with PanResponder, a spring that chases a moving lead value (tracking), and a diffClamp-driven collapsing header.

SymbioteNative’s Animated is the RN Animated API, not Reanimated — there is no worklet system, no useSharedValue/useAnimatedStyle, and no gesture-handler integration beyond what PanResponder already provides. react-native-reanimated itself is not tested against SymbioteNative’s engine; it hooks much deeper into RN’s runtime than a plain native-view library does, so treat it as unverified rather than assuming it works the same way a wrapped third-party native view would.