UTA DevHub

Animation Assets Guide

Comprehensive guide for implementing and optimizing animations in React Native applications including Lottie animations, video assets, performance optimization, and memory management

Animation Assets Guide

Overview

Animations enhance user experience but can significantly impact app performance if not implemented properly. This guide covers Lottie animations, video assets, performance optimization, and memory management strategies for React Native applications.

Scope: This guide focuses on animation assets (Lottie, videos, GIFs) for brand experiences. For UI micro-interactions and transitions, see the UI Development guides.

Lottie Animations

Getting Started with Lottie

Install Dependencies

Add the required packages for Lottie support

# For Expo projects
npx expo install lottie-react-native
 
# For React Native CLI projects
npm install lottie-react-native
npx react-native link lottie-react-native

Basic Implementation

// components/LottieAnimation.tsx
import React from 'react';
import LottieView from 'lottie-react-native';
 
interface LottieAnimationProps {
  source: any;
  autoPlay?: boolean;
  loop?: boolean;
  style?: any;
}
 
export const LottieAnimation: React.FC<LottieAnimationProps> = ({
  source,
  autoPlay = true,
  loop = false,
  style,
}) => {
  return (
    <LottieView
      source={source}
      autoPlay={autoPlay}
      loop={loop}
      style={style}
    />
  );
};

Asset Organization

Organize Lottie files in a structured manner

assets/animations/
├── onboarding/
│   ├── welcome.json
│   ├── features.json
│   └── complete.json
├── loaders/
│   ├── spinner.json
│   └── progress.json
└── feedback/
    ├── success.json
    ├── error.json
    └── celebration.json

Advanced Lottie Implementation

Controlled Animation with Refs

// components/ControlledLottieView.tsx
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
import LottieView from 'lottie-react-native';
 
export interface LottieAnimationHandle {
  play: () => void;
  pause: () => void;
  reset: () => void;
}
 
export const ControlledLottieView = forwardRef<
  LottieAnimationHandle, 
  any
>(({ source, style, onAnimationFinish }, ref) => {
  const animationRef = useRef<LottieView>(null);
  
  useImperativeHandle(ref, () => ({
    play: () => animationRef.current?.play(),
    pause: () => animationRef.current?.pause(),
    reset: () => animationRef.current?.reset(),
  }));
  
  return (
    <LottieView
      ref={animationRef}
      source={source}
      style={style}
      onAnimationFinish={onAnimationFinish}
      autoPlay={false}
    />
  );
});

Video Assets

Video Implementation

// components/VideoPlayer.tsx
import React, { useRef, useState } from 'react';
import { View, StyleSheet } from 'react-native';
import Video from 'react-native-video';
 
interface VideoPlayerProps {
  source: any;
  autoPlay?: boolean;
  loop?: boolean;
  muted?: boolean;
  onLoadStart?: () => void;
  onLoad?: () => void;
  onError?: (error: any) => void;
}
 
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
  source,
  autoPlay = false,
  loop = false,
  muted = true,
  onLoadStart,
  onLoad,
  onError,
}) => {
  const videoRef = useRef<Video>(null);
  const [paused, setPaused] = useState(!autoPlay);
 
  return (
    <View style={styles.container}>
      <Video
        ref={videoRef}
        source={source}
        style={styles.video}
        paused={paused}
        repeat={loop}
        muted={muted}
        resizeMode="cover"
        onLoadStart={onLoadStart}
        onLoad={onLoad}
        onError={onError}
        playInBackground={false}
        playWhenInactive={false}
      />
    </View>
  );
};
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  video: {
    position: 'absolute',
    top: 0,
    left: 0,
    bottom: 0,
    right: 0,
  },
});

Video Optimization

Video Format and Quality

# Optimize video for mobile
ffmpeg -i input.mp4 \
  -c:v libx264 \
  -preset slow \
  -crf 23 \
  -c:a aac \
  -b:a 128k \
  -movflags +faststart \
  output.mp4
 
# Create multiple quality versions
# Low quality (for slow connections)
ffmpeg -i input.mp4 -s 480x270 -b:v 500k output_low.mp4
 
# Medium quality
ffmpeg -i input.mp4 -s 720x405 -b:v 1000k output_medium.mp4
 
# High quality
ffmpeg -i input.mp4 -s 1080x607 -b:v 2000k output_high.mp4

Animation Performance Optimization

Animation Asset Optimization

Lottie File Optimization

# Install optimization tools
npm install -g @lottiefiles/lottie-compress
 
# Optimize Lottie files
lottie-compress input.json --output output.json --quality 0.8

Asset Size Analysis

// scripts/analyze-animations.ts
import fs from 'fs';
import path from 'path';
 
interface AnimationAnalysis {
  path: string;
  size: number;
  type: 'lottie' | 'video' | 'gif';
  recommendations: string[];
}
 
export class AnimationAssetAnalyzer {
  analyze(): AnimationAnalysis[] {
    const animationsDir = 'assets/animations';
    const files = this.getAllAnimationFiles(animationsDir);
    
    return files.map(file => this.analyzeFile(file));
  }
  
  private analyzeFile(filePath: string): AnimationAnalysis {
    const stats = fs.statSync(filePath);
    const ext = path.extname(filePath).toLowerCase();
    const size = stats.size;
    
    const type = this.getAnimationType(ext);
    const recommendations = this.generateRecommendations(type, size);
    
    return {
      path: filePath,
      size,
      type,
      recommendations,
    };
  }
  
  private generateRecommendations(type: string, size: number): string[] {
    const recommendations = [];
    
    if (type === 'lottie' && size > 100 * 1024) {
      recommendations.push('Large Lottie file - consider optimization');
    }
    
    if (type === 'video' && size > 5 * 1024 * 1024) {
      recommendations.push('Large video file - consider compression');
    }
    
    return recommendations;
  }
}

Runtime Optimization

// core/performance/AnimationManager.ts
export class AnimationManager {
  private static activeAnimations = new Set<string>();
  private static maxConcurrentAnimations = 3;
  
  static canPlayAnimation(id: string): boolean {
    return this.activeAnimations.size < this.maxConcurrentAnimations;
  }
  
  static registerAnimation(id: string): void {
    if (this.canPlayAnimation(id)) {
      this.activeAnimations.add(id);
    }
  }
  
  static unregisterAnimation(id: string): void {
    this.activeAnimations.delete(id);
  }
}

Memory Management

Animation Memory Optimization

// hooks/useAnimationMemoryManager.ts
import { useEffect, useRef } from 'react';
import { AppState } from 'react-native';
 
export const useAnimationMemoryManager = (animationId: string) => {
  const animationRef = useRef<any>(null);
  
  useEffect(() => {
    const subscription = AppState.addEventListener('change', (nextAppState) => {
      if (nextAppState === 'background') {
        animationRef.current?.pause();
      } else if (nextAppState === 'active') {
        animationRef.current?.play();
      }
    });
    
    return () => {
      subscription?.remove();
      animationRef.current = null;
    };
  }, []);
  
  return animationRef;
};

Performance Monitoring

// utils/AnimationPerformanceMonitor.ts
export class AnimationPerformanceMonitor {
  private static metrics = {
    lottieAnimations: [] as Array<{name: string; loadTime: number; fps: number}>,
    videoPlays: [] as Array<{name: string; loadTime: number; resolution: string}>,
  };
  
  static recordLottiePerformance(name: string, loadTime: number, fps: number): void {
    this.metrics.lottieAnimations.push({ name, loadTime, fps });
    
    if (fps < 30) {
      console.warn(`Low FPS Lottie animation: ${name} at ${fps} FPS`);
    }
  }
  
  static recordVideoPerformance(name: string, loadTime: number, resolution: string): void {
    this.metrics.videoPlays.push({ name, loadTime, resolution });
    
    if (loadTime > 3000) {
      console.warn(`Slow video load: ${name} took ${loadTime}ms`);
    }
  }
  
  static getPerformanceReport() {
    const { lottieAnimations, videoPlays } = this.metrics;
    
    return {
      lottie: {
        total: lottieAnimations.length,
        averageFPS: lottieAnimations.reduce((a, b) => a + b.fps, 0) / lottieAnimations.length,
        lowPerformanceAnimations: lottieAnimations.filter(a => a.fps < 30),
      },
      video: {
        total: videoPlays.length,
        averageLoadTime: videoPlays.reduce((a, b) => a + b.loadTime, 0) / videoPlays.length,
        slowLoads: videoPlays.filter(v => v.loadTime > 3000),
      },
    };
  }
}

Best Practices

Animation Implementation

(Do ✅):

  • Optimize Lottie JSON files before use
  • Use appropriate render modes for different platforms
  • Pause animations when app is backgrounded
  • Limit concurrent complex animations
  • Implement proper memory management

(Don't ❌):

  • Use unoptimized animation files from design tools
  • Run multiple heavy animations simultaneously
  • Keep animations playing when not visible
  • Ignore memory management for video assets

Video Asset Management

(Do ✅):

  • Provide multiple quality options for different network conditions
  • Use poster images for video thumbnails
  • Properly manage video memory and lifecycle
  • Optimize video compression for mobile

(Don't ❌):

  • Auto-play videos on cellular connections
  • Keep video components mounted when not needed
  • Use high-resolution videos for background animations
  • Ignore platform-specific video optimizations

Common Use Cases

Onboarding Animations

// components/OnboardingAnimation.tsx
import React, { useRef, useEffect } from 'react';
import { ControlledLottieView, LottieAnimationHandle } from './ControlledLottieView';
 
interface OnboardingAnimationProps {
  step: number;
  onComplete: () => void;
}
 
export const OnboardingAnimation: React.FC<OnboardingAnimationProps> = ({
  step,
  onComplete,
}) => {
  const animationRef = useRef<LottieAnimationHandle>(null);
  
  const animations = [
    require('@/assets/animations/onboarding/welcome.json'),
    require('@/assets/animations/onboarding/features.json'),
    require('@/assets/animations/onboarding/complete.json'),
  ];
  
  useEffect(() => {
    animationRef.current?.reset();
    animationRef.current?.play();
  }, [step]);
  
  return (
    <ControlledLottieView
      ref={animationRef}
      source={animations[step]}
      onAnimationFinish={onComplete}
      style={{ width: 300, height: 300 }}
    />
  );
};

Loading States

// components/LoadingAnimation.tsx
import React from 'react';
import { OptimizedLottieView } from './OptimizedLottieView';
 
interface LoadingAnimationProps {
  size?: 'small' | 'medium' | 'large';
}
 
export const LoadingAnimation: React.FC<LoadingAnimationProps> = ({
  size = 'medium',
}) => {
  const sizeMap = {
    small: { width: 40, height: 40 },
    medium: { width: 80, height: 80 },
    large: { width: 120, height: 120 },
  };
  
  return (
    <OptimizedLottieView
      source={require('@/assets/animations/loading-spinner.json')}
      autoPlay
      loop
      style={sizeMap[size]}
    />
  );
};

Remember: Animations should enhance user experience, not hinder performance. Always test on lower-end devices.