exodeui-react-native
v6.9.4
Published
React Native runtime for ExodeUI animations
Maintainers
Readme
exodeui-react-native
The official React Native Runtime for the ExodeUI Animation Engine. Render complex, high-performance interactive animations natively using Shopify's React Native Skia.
🌟 Features & Specialties
ExodeUI isn't just another animation player. It brings game-engine capabilities to your React Native application:
- 120 FPS Skia Rendering: Hardware-accelerated graphics via React Native Skia for butter-smooth performance.
- True 3D Transforms: Full 3D rotation (X, Y, Z), depth translation, and perspective projection in a 2D canvas.
- Physics Engine & Constraints: Built-in 2D rigid body physics (Matter.js) with Inverse Kinematics (IK), Follow Path, and Spring constraints.
- Interactive State Machines: Complex state logic (Hover, Pressed, Toggled) designed in the editor, driven at runtime.
- Rich UI Components: Buttons, Toggle Switches, Sliders, Dropdowns, ListViews, and dynamic Line/Bar/Pie Charts — all rendered inside the canvas.
- Advanced Masking & Blending: Boolean operations, nested clipping masks, and blend modes.
- Liquid Morphing & Path Distortion: Real-time noise-based path distortions and fluid shape morphing.
Installation
npm install exodeui-react-native @shopify/react-native-skia
# or
yarn add exodeui-react-native @shopify/react-native-skiaSetup
1. Install React Native Skia
Configure @shopify/react-native-skia following their installation guide.
2. Configure Metro Bundler
Wrap your metro.config.js with the withExode utility so Metro can resolve .exode files natively:
// metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const { withExode } = require('exodeui-react-native/withExode');
const defaultConfig = getDefaultConfig(__dirname);
const config = {
// Your other metro config here
};
module.exports = withExode(mergeConfig(defaultConfig, config));Then restart the packager with a clean cache:
npx react-native start --reset-cacheBasic Usage
import { ExodeUIView } from 'exodeui-react-native';
import splashAnimation from './assets/splash.exode';
function App() {
return (
<ExodeUIView
artboard={splashAnimation}
style={{ width: '100%', height: 400 }}
autoPlay={true}
/>
);
}You can also load from a remote URL:
<ExodeUIView
src="https://example.com/animations/splash.json"
style={{ flex: 1 }}
/><ExodeUIView /> Props
Core Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| artboard | object | — | Direct artboard data object (e.g. imported from a .exode file). |
| src | string | — | Remote URL to fetch artboard JSON from. |
| style | ViewStyle | — | Styles applied to the container View. |
| autoPlay | boolean | true | Whether the animation loop runs automatically. |
| fit | 'Contain' \| 'Cover' \| 'Fill' | 'Contain' | How the artboard scales inside the view. |
| alignment | 'Center' \| 'TopLeft' \| 'TopRight' \| 'BottomLeft' \| 'BottomRight' | 'Center' | Alignment of the artboard within the view. |
| interactive | boolean | true | Whether touch/pointer events are forwarded to the engine. |
| revision | number | 0 | Increment this to force a full artboard reload. |
| currentTime | number | — | Manually seek the animation to a specific time (in seconds). |
Input Binding Props
Pass reactive values directly as props — no onReady needed:
| Prop | Type | Description |
|------|------|-------------|
| inputs | Record<string, any> | Map of input names to values. Updated reactively. |
| scrollY | number | Shorthand for binding vertical scroll position. |
| scrollX | number | Shorthand for binding horizontal scroll position. |
| mouseX | number | Pointer X position in canvas coordinates. |
| mouseY | number | Pointer Y position in canvas coordinates. |
| isMouseDown | boolean | Whether a pointer is currently pressed. |
Event Listener Props
| Prop | Type | Description |
|------|------|-------------|
| onReady | (engine: ExodeUIEngine) => void | Called once the engine has loaded the artboard and is ready. |
| onButtonClick | (name: string, objectId: string) => void | Fired when a Button component is tapped. |
| onToggle | (name: string, checked: boolean) => void | Fired when a Toggle Switch is flipped. |
| onInputChange | (name: string, text: string) => void | Fired when text inside an InputBox changes. |
| onInputFocus | (name: string) => void | Fired when an InputBox receives focus. |
| onInputBlur | (name: string) => void | Fired when an InputBox loses focus. |
| onGraphPointClick | (event: GraphPointClickEvent) => void | Fired when a point on a Line/Bar chart is tapped. |
| onTrigger | (triggerName: string, animationName: string) => void | Fired when a state machine trigger activates. |
| onInputUpdate | (nameOrId: string, value: any) => void | Fired whenever any input value changes internally. |
| onComponentChange | (event: ComponentEvent) => void | Fired on any generic component state change. |
| onLogicUpdate | (values: Record<string, any>) => void | Fires every frame with the current state of all logic node outputs. |
| onLog | (msg: string) => void | Receives internal engine log messages (useful for debugging). |
ExodeUIEngine (via onReady)
The engine object gives you full imperative control over the artboard at runtime.
State Variables (Inputs)
State Variables (referred to internally as "Inputs") allow you to drive the logic of your state machines from your React Native code. ExodeUI supports several types of state variables:
- Boolean: Simple
true/falseswitches. - Number: Float or integer values (e.g. 0 to 100).
- Trigger: Momentary pulses that auto-reset (e.g. for "Jump" or "Click").
- Array: Arrays of numbers or strings (e.g. for charts or list data).
- Random Number / Random Color: Engine-managed variables that dynamically evaluate based on seeds and time.
Setting State Variables
// Set a boolean state variable (e.g. "IsMenuOpen")
engine.updateInput('IsMenuOpen', true);
// Set a number state variable (e.g. "Speed")
engine.updateInput('Speed', 42);
// Fire a trigger (momentary pulse)
engine.updateInput('Jump', true); // Trigger types auto-pulse
// Set an Array
engine.setInputNumberArray('ChartData', [10, 20, 30, 40]);Reading State Variables
You can also read the current value of any state variable at runtime using engine.getInputValue(). This works for standard variables you set, as well as dynamic ones like Random Number and Random Color evaluated internally by the engine:
// Read a boolean, number, array, or dynamically generated variable
const isMenuOpen = engine.getInputValue('IsMenuOpen');
const randomVal = engine.getInputValue('MyRandomNumber'); // Read engine-managed random value
console.log(`Menu is open: ${isMenuOpen}, Random: ${randomVal}`);Data & Component Updates
// Update a LineGraph or BarChart dataset
engine.updateGraphData('RevenueChart', [120, 340, 210, 480, 390]);
// Update options for a Dropdown or ListView
engine.updateObjectOptions('CategoryDropdown', ['Electronics', 'Clothing', 'Food']);
// Modify a constraint at runtime (e.g. spring stiffness)
engine.updateConstraint('SpringArm', 0, { stiffness: 300, damping: 20 });Layout & Playback
// Change fit/alignment dynamically
engine.setLayout('Cover', 'TopLeft');
// Seek to a specific time
engine.seek(2.5); // 2.5 seconds
// Reset all states to artboard defaults
engine.reset();Reading State
// Get currently active state IDs for a layer
const activeStates = engine.getActiveStateIds('NavigationLayer');
console.log(activeStates); // e.g. ['state_home']Direct Listeners (alternative to props)
engine.setButtonClickCallback((name, id) => { ... });
engine.setToggleCallback((name, checked) => { ... });
engine.setInputChangeCallback((name, text) => { ... });
engine.setInputFocusCallback((name) => { ... });
engine.setInputBlurCallback((name) => { ... });
engine.setGraphPointClickCallback((event) => { ... });
engine.setTriggerCallback((triggerName, animationName) => { ... });
engine.setInputUpdateCallback((nameOrId, value) => { ... });
engine.setComponentChangeCallback((event) => { ... });
engine.setOnLogCallback((msg) => { ... });
engine.setLogicUpdateCallback((values) => { ... });ref Access
Use a ref to access the engine imperatively without onReady:
const viewRef = useRef(null);
// Later:
const engine = viewRef.current?.getEngine();
engine?.updateInput('IsVisible', true);
// In JSX:
<ExodeUIView ref={viewRef} artboard={data} style={{ flex: 1 }} />Complete Examples
1. Button Click → Navigate
<ExodeUIView
artboard={myArtboard}
style={{ flex: 1 }}
onButtonClick={(name, id) => {
if (name === 'btn_start') navigation.navigate('Home');
}}
/>2. Toggle Switch → Update App State
<ExodeUIView
artboard={settingsArtboard}
style={{ height: 300, width: '100%' }}
onToggle={(name, checked) => {
if (name === 'DarkModeToggle') setDarkMode(checked);
}}
/>3. Real-time Scroll Binding
const scrollY = useSharedValue(0);
<Animated.ScrollView
onScroll={(e) => { scrollY.value = e.nativeEvent.contentOffset.y; }}
>
<ExodeUIView
artboard={headerArtboard}
style={{ height: 200, width: '100%' }}
scrollY={scrollY.value}
/>
</Animated.ScrollView>4. Live Data Chart
<ExodeUIView
artboard={dashboardArtboard}
style={{ flex: 1 }}
onReady={(engine) => {
// Push live sensor data every second
setInterval(() => {
engine.updateGraphData('HeartRate', getLiveSensorData());
}, 1000);
}}
/>5. State Machine — Driving an Interactive Game UI
<ExodeUIView
artboard={gameHUD}
style={{ flex: 1 }}
inputs={{ Health: playerHealth, Score: score, IsGameOver: isGameOver }}
onTrigger={(triggerName, animation) => {
if (triggerName === 'LevelComplete') showLevelModal();
}}
/>6. Listening to Graph Point Taps
<ExodeUIView
artboard={chartArtboard}
style={{ height: 300, width: '100%' }}
onGraphPointClick={({ pointIndex, value, label }) => {
Alert.alert(`Point ${label}`, `Value: ${value}`);
}}
/>7. InputBox (Text Field inside Canvas)
<ExodeUIView
artboard={loginArtboard}
style={{ flex: 1 }}
onInputChange={(name, text) => {
if (name === 'EmailField') setEmail(text);
if (name === 'PasswordField') setPassword(text);
}}
onButtonClick={(name) => {
if (name === 'LoginButton') handleLogin();
}}
/>TypeScript Types
import type { ExodeUIEngine, ExodeUIViewProps } from 'exodeui-react-native';
// GraphPointClickEvent shape
type GraphPointClickEvent = {
objectId: string;
componentName: string;
datasetIndex: number;
pointIndex: number;
value: number;
label: string;
};License
MIT
