UTA DevHub
UI Development/Splash Screen Guide

Advanced Topics

Advanced splash screen implementations including custom animations, abstractions, and performance optimization

Advanced Topics

Overview

This guide covers advanced splash screen implementations for developers who need more control over the splash screen behavior, custom animations, or specific performance optimizations.

When to Use Advanced Features

Consider these advanced techniques when:

  • You need custom animations beyond simple fades
  • Building a unique brand experience
  • Optimizing for specific performance requirements
  • Creating interactive splash screens
  • Implementing complex loading sequences

Custom Animations

Native Hide Animations

react-native-bootsplash provides hooks for custom hide animations:

import { useEffect, useState } from 'react';
import { Animated, Dimensions } from 'react-native';
import RNBootSplash from 'react-native-bootsplash';
 
export function AnimatedSplashScreen({ onAnimationEnd }) {
  const [opacity] = useState(new Animated.Value(1));
  const [scale] = useState(new Animated.Value(1));
  
  useEffect(() => {
    // Wait for app to be ready, then animate
    Animated.parallel([
      Animated.timing(opacity, {
        toValue: 0,
        duration: 400,
        useNativeDriver: true,
      }),
      Animated.timing(scale, {
        toValue: 0.8,
        duration: 400,
        useNativeDriver: true,
      }),
    ]).start(() => {
      RNBootSplash.hide();
      onAnimationEnd();
    });
  }, []);
  
  return (
    <Animated.View
      style={{
        ...StyleSheet.absoluteFillObject,
        opacity,
        transform: [{ scale }],
        backgroundColor: '#FFFFFF',
        alignItems: 'center',
        justifyContent: 'center',
      }}
    >
      <Image source={require('@/assets/logo.png')} />
    </Animated.View>
  );
}

Interactive Splash Screens

Create splash screens that respond to user interaction:

import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  runOnJS,
} from 'react-native-reanimated';
 
export function InteractiveSplash({ onComplete }) {
  const translateY = useSharedValue(0);
  const opacity = useSharedValue(1);
  
  const gesture = Gesture.Pan()
    .onUpdate((event) => {
      translateY.value = Math.max(0, event.translationY);
      opacity.value = 1 - Math.abs(translateY.value) / 300;
    })
    .onEnd(() => {
      if (Math.abs(translateY.value) > 100) {
        translateY.value = withSpring(300, {}, () => {
          runOnJS(onComplete)();
        });
        opacity.value = withSpring(0);
      } else {
        translateY.value = withSpring(0);
        opacity.value = withSpring(1);
      }
    });
  
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ translateY: translateY.value }],
    opacity: opacity.value,
  }));
  
  return (
    <GestureDetector gesture={gesture}>
      <Animated.View style={[styles.container, animatedStyle]}>
        <Image source={require('@/assets/logo.png')} />
        <Text style={styles.hint}>Swipe up to start</Text>
      </Animated.View>
    </GestureDetector>
  );
}

Abstraction Layer

Create a comprehensive abstraction layer for splash screen functionality:

Complete Abstraction Implementation

// core/shared/splash-screen/SplashScreenManager.ts
import { Platform } from 'react-native';
import RNBootSplash from 'react-native-bootsplash';
// Alternative imports for different libraries
// import SplashScreen from 'react-native-splash-screen';
// import * as ExpoSplashScreen from 'expo-splash-screen';
 
export interface SplashScreenConfig {
  fade?: boolean;
  duration?: number;
  delay?: number;
}
 
export interface SplashScreenProvider {
  show(): Promise<void>;
  hide(config?: SplashScreenConfig): Promise<void>;
  isVisible(): Promise<boolean>;
  setProgress?(progress: number): void;
}
 
// RNBootSplash implementation
class RNBootSplashProvider implements SplashScreenProvider {
  async show(): Promise<void> {
    return RNBootSplash.show();
  }
  
  async hide(config?: SplashScreenConfig): Promise<void> {
    const { fade = true, duration = 500, delay = 0 } = config || {};
    
    if (delay > 0) {
      await new Promise(resolve => setTimeout(resolve, delay));
    }
    
    return RNBootSplash.hide({ fade, duration });
  }
  
  async isVisible(): Promise<boolean> {
    return RNBootSplash.isVisible();
  }
  
  setProgress(progress: number): void {
    // RNBootSplash doesn't support progress natively
    // Could emit events for custom implementation
  }
}
 
// Factory pattern for provider selection
class SplashScreenFactory {
  static createProvider(type: 'rnbootsplash' | 'expo' | 'legacy'): SplashScreenProvider {
    switch (type) {
      case 'rnbootsplash':
        return new RNBootSplashProvider();
      // Add other providers as needed
      default:
        return new RNBootSplashProvider();
    }
  }
}
 
// Main abstraction
export class SplashScreenManager {
  private static provider: SplashScreenProvider = SplashScreenFactory.createProvider('rnbootsplash');
  private static config: SplashScreenConfig = {};
  
  /**
   * Configure splash screen behavior
   */
  static configure(config: SplashScreenConfig): void {
    this.config = { ...this.config, ...config };
  }
  
  /**
   * Show splash screen (usually for development)
   */
  static async show(): Promise<void> {
    return this.provider.show();
  }
  
  /**
   * Hide splash screen with configuration
   */
  static async hide(config?: SplashScreenConfig): Promise<void> {
    const mergedConfig = { ...this.config, ...config };
    return this.provider.hide(mergedConfig);
  }
  
  /**
   * Check if splash screen is visible
   */
  static async isVisible(): Promise<boolean> {
    return this.provider.isVisible();
  }
  
  /**
   * Update progress (if supported)
   */
  static setProgress(progress: number): void {
    this.provider.setProgress?.(progress);
  }
  
  /**
   * Platform-specific hide with smart defaults
   */
  static async smartHide(): Promise<void> {
    const config: SplashScreenConfig = Platform.select({
      ios: { fade: true, duration: 400 },
      android: { fade: true, duration: 500 },
      default: { fade: true, duration: 500 },
    });
    
    return this.hide(config);
  }
}

Usage with Abstraction

// App initialization
import { SplashScreenManager } from '@/core/shared/splash-screen';
 
// Configure once
SplashScreenManager.configure({
  fade: true,
  duration: 500,
});
 
// In your app
async function initializeApp() {
  try {
    // Initialization tasks
    await initializeServices();
    
    // Hide with smart defaults
    await SplashScreenManager.smartHide();
  } catch (error) {
    // Always hide on error
    await SplashScreenManager.hide({ duration: 0 });
  }
}

Performance Optimization

Preloading Assets

Optimize splash screen transitions by preloading critical assets:

Create Asset Preloader

// core/shared/assets/AssetPreloader.ts
import { Image } from 'react-native';
import { Asset } from 'expo-asset';
 
export class AssetPreloader {
  private static imageAssets = [
    require('@/assets/logo.png'),
    require('@/assets/onboarding/step1.png'),
    require('@/assets/onboarding/step2.png'),
  ];
  
  private static fontAssets = {
    'Inter-Regular': require('@/assets/fonts/Inter-Regular.ttf'),
    'Inter-Bold': require('@/assets/fonts/Inter-Bold.ttf'),
  };
  
  static async preloadImages(): Promise<void> {
    const imagePromises = this.imageAssets.map(image => {
      return Image.prefetch(Image.resolveAssetSource(image).uri);
    });
    
    await Promise.all(imagePromises);
  }
  
  static async preloadFonts(): Promise<void> {
    // For Expo
    await Font.loadAsync(this.fontAssets);
    
    // For bare React Native, fonts are usually linked
  }
  
  static async preloadAll(): Promise<void> {
    await Promise.all([
      this.preloadImages(),
      this.preloadFonts(),
    ]);
  }
}

Integrate with Splash Screen

export default function App() {
  const [assetsLoaded, setAssetsLoaded] = useState(false);
  
  useEffect(() => {
    async function loadAssets() {
      try {
        // Start preloading while splash is visible
        await AssetPreloader.preloadAll();
        setAssetsLoaded(true);
        
        // Hide splash after assets are ready
        await SplashScreenManager.hide();
      } catch (error) {
        console.error('Asset preload failed:', error);
        // Hide splash anyway
        await SplashScreenManager.hide();
      }
    }
    
    loadAssets();
  }, []);
  
  if (!assetsLoaded) {
    return null; // Keep splash visible
  }
  
  return <MainApp />;
}

Memory Management

Optimize memory usage during splash screen:

// Defer heavy imports until after splash
const LazyMainApp = lazy(() => import('./MainApp'));
 
export default function App() {
  const [splashHidden, setSplashHidden] = useState(false);
  
  useEffect(() => {
    // Minimal initialization
    async function init() {
      // Only critical tasks
      await initializeCriticalServices();
      
      // Hide splash
      await SplashScreenManager.hide();
      setSplashHidden(true);
      
      // Heavy initialization after splash
      setTimeout(() => {
        initializeNonCriticalServices();
      }, 100);
    }
    
    init();
  }, []);
  
  return (
    <Suspense fallback={null}>
      {splashHidden && <LazyMainApp />}
    </Suspense>
  );
}

Debugging & Troubleshooting

Debug Mode

Create a debug mode for splash screen development:

// core/shared/splash-screen/SplashDebug.ts
export class SplashDebug {
  private static enabled = __DEV__;
  private static startTime = Date.now();
  
  static log(message: string, data?: any): void {
    if (!this.enabled) return;
    
    const elapsed = Date.now() - this.startTime;
    console.log(`[Splash ${elapsed}ms] ${message}`, data || '');
  }
  
  static async measureHideTime(hideFunction: () => Promise<void>): Promise<void> {
    const start = Date.now();
    await hideFunction();
    const duration = Date.now() - start;
    
    this.log(`Hide animation completed in ${duration}ms`);
  }
  
  static simulateSlowInit(ms: number = 3000): Promise<void> {
    this.log(`Simulating ${ms}ms initialization`);
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}
 
// Usage
await SplashDebug.measureHideTime(() => 
  SplashScreenManager.hide({ fade: true })
);

Performance Monitoring

// Monitor splash screen performance
export class SplashMetrics {
  private static metrics = {
    showTime: 0,
    hideTime: 0,
    totalDuration: 0,
  };
  
  static recordShow(): void {
    this.metrics.showTime = Date.now();
  }
  
  static recordHide(): void {
    this.metrics.hideTime = Date.now();
    this.metrics.totalDuration = this.metrics.hideTime - this.metrics.showTime;
    
    // Send to analytics
    Analytics.track('splash_screen_duration', {
      duration: this.metrics.totalDuration,
      platform: Platform.OS,
    });
  }
}

Advanced Patterns

Conditional Splash Screens

Show different splash screens based on conditions:

export function ConditionalSplash({ children }) {
  const [splashType, setSplashType] = useState<'loading' | 'update' | 'maintenance' | null>('loading');
  
  useEffect(() => {
    async function checkConditions() {
      // Check for app updates
      if (await UpdateManager.hasRequiredUpdate()) {
        setSplashType('update');
        return;
      }
      
      // Check for maintenance
      if (await API.isUnderMaintenance()) {
        setSplashType('maintenance');
        return;
      }
      
      // Normal initialization
      await initializeApp();
      setSplashType(null);
    }
    
    checkConditions();
  }, []);
  
  switch (splashType) {
    case 'loading':
      return <LoadingSplash />;
    case 'update':
      return <UpdateRequiredSplash />;
    case 'maintenance':
      return <MaintenanceSplash />;
    default:
      return children;
  }
}

Splash Screen A/B Testing

Test different splash screen experiences:

export function ABTestSplash({ onComplete }) {
  const [variant] = useState(() => 
    Math.random() > 0.5 ? 'animated' : 'static'
  );
  
  useEffect(() => {
    Analytics.track('splash_screen_viewed', { variant });
  }, [variant]);
  
  if (variant === 'animated') {
    return <AnimatedSplashScreen onComplete={onComplete} />;
  }
  
  return <StaticSplashScreen onComplete={onComplete} />;
}

Best Practices

Advanced Implementation Tips

  1. Keep animations smooth: Target 60 FPS
  2. Test on low-end devices: Ensure performance
  3. Handle edge cases: App backgrounding, crashes
  4. Monitor metrics: Track display duration
  5. Provide fallbacks: For animation failures

Performance Guidelines

  • (Do ✅) Use native driver for animations when possible
  • (Do ✅) Preload critical assets during splash display
  • (Do ✅) Defer heavy operations until after splash
  • (Don't ❌) Block the main thread during initialization
  • (Consider 🤔) Progressive loading for large apps

Summary

Advanced splash screen techniques enable unique brand experiences while maintaining performance:

  • Custom animations create memorable first impressions
  • Abstractions enable flexibility and maintainability
  • Performance optimization ensures smooth transitions
  • Debugging tools help identify issues
  • Advanced patterns handle complex requirements

Choose techniques that align with your app's needs and user expectations.