Skip to main content

Performance Guide

Overview

Aniview is built for 60+ FPS animation performance. This guide explains the architecture that makes it fast and how to get the most out of it.


How Aniview Is Fast

The Baking Pipeline

On mount, each Aniview runs a one-time pre-computation ("bake") that eliminates runtime work:

Mount (JS thread, once)              Every Frame (UI thread)
━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━
1. Grid construction 1. Segment lookup (O(1))
2. Lane organization (H + V) 2. Linear interpolation
3. Color normalization → rgba() 3. Composite result
4. Constant folding → No searching, no parsing

What this means in practice:

OperationWithout BakingWith Baking
Find frameO(n) per renderO(1) lookup
Parse colorsEvery frameNever (pre-normalized at bake)
InterpolationAll propertiesOnly dynamic properties

Segment Caching

Inside useAnimatedStyle, Aniview finds the interpolation segment once per frame, then reuses it for every property. So if a component has 20 animated properties, the segment search runs 1 time, not 20.

Constant Folding

During baking, Aniview detects when a property has the same value at every position along an axis. These properties are marked with constFlags and skip interpolation entirely at runtime.

frames={{
left: { page: 'PAGE_1', style: { opacity: 0, backgroundColor: 'red' } },
right: { page: 'PAGE_2', style: { opacity: 0, backgroundColor: 'red' } }
}}
// backgroundColor is the same at both positions → constFlag = true
// At render: skip interpolateColor() for backgroundColor entirely

Smart Color Interpolation

When transitioning between a solid color and transparent, standard interpolateColor produces a gray flash (the RGB channels decay to black). Aniview's smartInterpolateColor preserves the RGB of the non-transparent color and only fades the alpha:

Standard:     rgba(255,255,255,1) → rgba(0,0,0,0)     // Gray flash at midpoint
Aniview: rgba(255,255,255,1) → rgba(255,255,255,0) // Clean fade

Dual-Layer Virtualization

Aniview has two virtualization layers that work together automatically:

  1. Worklet-level (instant): Components more than 1.5× screen width from the camera are set to opacity: 0 on the UI thread. Zero React re-renders.

  2. JS-level (deferred): After a snap completes, far-away non-persistent components are unmounted from the React tree to reclaim memory.


Optimization Techniques

1. Frame Property Defaults

Properties specified in a frame but not in the base style are taken to be the same as the base style. Aniview uses value-based change detection — if a frame property's value matches the base, it's flagged as constant and skipped at runtime. This means redundant specifications are harmless, but unnecessary:

<Aniview
pageId="HOME"
style={{ width: 100, height: 100, borderRadius: 20 }}
frames={{
away: {
page: "PROFILE",
style: {
width: 100, // Same as base — will be detected as constant, not interpolated
height: 100, // Same as base — will be detected as constant, not interpolated
borderRadius: 20, // Same as base — will be detected as constant, not interpolated
opacity: 0, // Different from base — this is the only property that gets interpolated
},
},
}}
/>

Properties in style that don't appear in any frame are never interpolated. They're applied as-is.

2. Stabilize Dependencies

The bake runs inside useMemo. If its dependencies change, it re-bakes. Avoid creating new style objects on every render:

❌ Re-bakes every render:

<Aniview style={{ width: 100, backgroundColor: "red" }} />

✅ Stable — bakes once:

const stableStyle = useMemo(() => ({ width: 100, backgroundColor: "red" }), []);
<Aniview style={stableStyle} />;

Or use StyleSheet.create:

const styles = StyleSheet.create({
box: { width: 100, backgroundColor: "red" },
});
<Aniview style={styles.box} />;

3. Use persistent Sparingly

persistent={true} prevents unmounting, which means those components contribute to the render tree even when offscreen. Only use it for:

  • WebGL/Three.js canvases
  • Components with expensive internal state (video players, etc.)
  • Components with event-driven animations that must remain active cross-page

4. Keep Event Lanes Focused

Only pass events to AniviewProvider that are actually consumed by frames:

// ❌ Passing unused events
<AniviewProvider events={{ scrollY, zoom, rotation, tilt }}>

// ✅ Only what's needed
<AniviewProvider events={{ scrollY }}>

Each event drives a 1D interpolation lane. Unused events still consume a small amount of worklet overhead.


Presence-Weighted Events

Aniview modulates event-driven animations by presence — a value from 0 to 1 indicating how close the camera is to the component's home page. When you swipe away from a page, its event-driven effects fade out automatically.

This means you don't need to manually gate event effects. A scroll-linked header on Page 0 won't "leak" its scroll offset into Page 1's layout.

If you need an event to stay active even when far from the home page, use persistent: true on the event frame:

frames={{
scrolled: {
event: 'scrollY',
value: 100,
persistent: true, // Effect stays active on all pages
style: { opacity: 0.5 },
},
}}

Profiling & Debugging

Measure Bake Time

Bake time is usually 1-5ms. If you suspect it's excessive, wrap your component in a timing log:

const start = performance.now();
const baked = useMemo(() => performBake(), [deps]);
console.log(`Bake: ${performance.now() - start}ms`);

React DevTools Profiler

  1. Enable profiling in React DevTools
  2. Start recording → navigate between pages
  3. Look for:
    • Aniview render time: should be < 1ms
    • Bake phase: one-time mount cost
    • useAnimatedStyle callbacks: should be minimal

Worklet Debugging

In Reanimated 3.0+, console.log works inside worklets:

const animatedStyle = useAnimatedStyle(() => {
console.log(`Camera: ${events.x.value}, ${events.y.value}`);
return computeStyle();
});

Common Pitfalls

1. Too Many Aniview Components

If you have static content that doesn't animate, wrap it in a regular View:

<View>
<Text>Static header</Text>
<Aniview>
<DynamicContent />
</Aniview>
</View>

Only components that need position/style interpolation should be Aniview.

2. Inline Style Objects

As mentioned above, inline objects cause re-bakes on every render. Use StyleSheet.create or useMemo.

3. Redundant Frame Properties

Don't repeat the same value in style and frames — it wastes constant folding analysis time and adds noise.


Production Checklist

  • Static properties in style, dynamic in frames
  • Style objects stabilized (StyleSheet.create or useMemo)
  • persistent={true} only where necessary
  • Only needed events passed to AniviewProvider
  • Tested on low-end devices (iPhone 8, Android mid-range)
  • Profiled with React DevTools

Next Steps