modules/expo-bluesky-gif-view/README.md
An Expo module for displaying animated GIFs and WebP images with optimized performance and playback controls.
This module provides a custom view component for rendering animated GIFs with support for:
The module uses native platform libraries for optimal GIF rendering performance:
ios/GifView.swift - Main view implementation using SDAnimatedImageViewios/ExpoBlueskyGifViewModule.swift - Module definition and prop bindingsios/Util.swift - Cache configuration utilitiesApproach: Uses SDAnimatedImageView for hardware-accelerated GIF rendering. Images are cached to disk only (not memory) to avoid performance issues with SDAnimatedImage when loaded from memory. The view automatically cancels pending requests when scrolled off-screen and resumes loading when visible.
android/src/main/java/expo/modules/blueskygifview/GifView.kt - Main view implementationandroid/src/main/java/expo/modules/blueskygifview/ExpoBlueskyGifViewModule.kt - Module definitionandroid/src/main/java/expo/modules/blueskygifview/AppCompatImageViewExtended.kt - Custom ImageView with playback controlApproach: Uses Glide's disk cache strategy for loading animated GIFs. Placeholders are loaded with skipMemoryCache(true) to avoid cache bloat. The custom AppCompatImageViewExtended detects when animations are loaded via onDraw and manages the Animatable drawable lifecycle.
<video> elementsrc/GifView.web.tsxApproach: Uses a looping, muted video element to display GIFs. This provides better performance than image-based approaches on the web. The implementation tracks load state to fire the onPlayerStateChange event only once (since onCanPlay fires on every loop).
import {GifView} from 'expo-bluesky-gif-view'
function MyComponent() {
const gifRef = React.useRef<GifView>(null)
return (
<GifView
source="https://example.com/animated.gif"
placeholderSource="https://example.com/thumbnail.jpg"
autoplay={true}
onPlayerStateChange={(event) => {
console.log('Playing:', event.nativeEvent.isPlaying)
console.log('Loaded:', event.nativeEvent.isLoaded)
}}
ref={gifRef}
/>
)
}
source?: string - URL of the animated GIF/WebPplaceholderSource?: string - URL of a static placeholder image to show while loadingautoplay?: boolean - Whether to start playing automatically (default: true)onPlayerStateChange?: (event: GifViewStateChangeEvent) => void - Callback fired when playback state changesAll methods are async and return a Promise:
await gifRef.current?.playAsync()
await gifRef.current?.pauseAsync()
await gifRef.current?.toggleAsync()
// Prefetch GIFs into the cache (not supported on web)
await GifView.prefetchAsync([
'https://example.com/gif1.gif',
'https://example.com/gif2.gif'
])
The module requires SDWebImage and SDWebImageWebPCoder:
# ios/ExpoBlueskyGifView.podspec
s.dependency 'SDWebImage', '~> 5.21.0'
s.dependency 'SDWebImageWebPCoder', '~> 0.14.6'
The module uses Glide, kept in sync with expo-image version:
# android/build.gradle
implementation 'com.github.bumptech.glide:glide:4.13.2'
willMove(toWindow:) when scrolled off-screenonDetachedFromWindow(), resumes in onAttachedToWindow()SDAnimatedImage memory issuesSDAnimatedImageView.autoPlayAnimatedImage is explicitly set to false to prevent automatic animation on viewport entryAppCompatImageViewExtended manages Animatable drawable stateexpo-bluesky-gif-view/
├── index.ts # Module entry point
├── expo-module.config.json # Expo module configuration
├── src/
│ ├── GifView.types.ts # TypeScript type definitions
│ ├── GifView.tsx # Native implementation (iOS/Android)
│ └── GifView.web.tsx # Web implementation
├── ios/
│ ├── ExpoBlueskyGifView.podspec # CocoaPods spec
│ ├── ExpoBlueskyGifViewModule.swift # Module and prop definitions
│ ├── GifView.swift # iOS view implementation
│ └── Util.swift # Cache configuration
└── android/
├── build.gradle # Gradle build configuration
└── src/main/java/expo/modules/blueskygifview/
├── ExpoBlueskyGifViewModule.kt # Module and prop definitions
├── GifView.kt # Android view implementation
└── AppCompatImageViewExtended.kt # Custom ImageView for playback