Expo
There is no React Native port. Geometry stays in src/core. Animation stays in src/js. Expo hosts that DOM renderer in a native WebView on iOS and Android.
expo start --web is a browser. react-native-webview does not run there. It prints React Native WebView does not support this platform and stays blank. Use the Web tab: the same HTML in an iframe. Use the Native tab in Expo Go, iOS, or Android.
Do not install react-native-web or react-dom for the native mosaic. Expo DOM components ('use dom') require those packages and pull a second web React tree into the app. Skip them.
Do not rebuild the lanes with Reanimated, Skia, or Animated. The renderer already uses CSS translate3d @keyframes. WKWebView and Android WebView composite those transforms on the GPU. See No frames! Pure CSS.
Install images-in-motion and the WebView:
npx expo install react-native-webviewbunx expo install react-native-webviewyarn expo install react-native-webviewpnpm expo install react-native-webviewcreateImagesInMotionWebViewHtml writes a full HTML document: a #stage, the IIFE from unpkg, and ImagesInMotion.mountImagesInMotion with your iimOptions. On iOS, source= sizes the document to #stage unless you pass viewportWidth / viewportHeight from the native onLayout box. The Native snippet does that. The WebView is flex: 1. rem is CSS inside that document, not a React Native style. #stage is 20rem by 20rem and centered in that measured box. originWhitelist={['*']} is required for an html source. Replace App.js or App.tsx with the Native snippet on device. On web, replace it with the Web snippet, or add App.web.js so Expo picks it automatically.
import { useState } from 'react'
import { View } from 'react-native'
import { WebView } from 'react-native-webview'
import { createImagesInMotionWebViewHtml } from 'images-in-motion'
const images = [
'https://picsum.photos/800/1200?random=1',
'https://picsum.photos/1200/800?random=2',
'https://picsum.photos/800/800?random=3',
'https://picsum.photos/1000/700?random=4',
'https://picsum.photos/800/1000?random=5',
'https://picsum.photos/700/1100?random=6',
'https://picsum.photos/800/1200?random=7',
'https://picsum.photos/1200/800?random=8',
]
const iimOptions = { images, speedRange: [8, 18], angle: 12 }
export default function App() {
const [viewport, setViewport] = useState({ width: 0, height: 0 })
const html = viewport.width > 0 && viewport.height > 0
? createImagesInMotionWebViewHtml(iimOptions, undefined, {
width: '20rem',
height: '20rem',
viewportWidth: viewport.width,
viewportHeight: viewport.height,
})
: ''
return (
<View
style={{ flex: 1 }}
onLayout={(event) => {
const { width, height } = event.nativeEvent.layout
setViewport((current) => (
current.width === width && current.height === height ? current : { width, height }
))
}}
>
{html ? (
<WebView
originWhitelist={['*']}
source={{ html }}
style={{ flex: 1, backgroundColor: 'transparent' }}
scrollEnabled={false}
automaticallyAdjustContentInsets={false}
contentInsetAdjustmentBehavior="never"
/>
) : null}
</View>
)
}
import { createElement } from 'react'
import { createImagesInMotionWebViewHtml } from 'images-in-motion'
const images = [
'https://picsum.photos/800/1200?random=1',
'https://picsum.photos/1200/800?random=2',
'https://picsum.photos/800/800?random=3',
'https://picsum.photos/1000/700?random=4',
'https://picsum.photos/800/1000?random=5',
'https://picsum.photos/700/1100?random=6',
'https://picsum.photos/800/1200?random=7',
'https://picsum.photos/1200/800?random=8',
]
const html = createImagesInMotionWebViewHtml(
{ images, speedRange: [8, 18], angle: 12 },
undefined,
{ width: '20rem', height: '20rem' },
)
export default function App() {
return createElement(
'div',
{ style: { width: '100%', height: '100%', minHeight: '100dvh', display: 'flex', alignItems: 'center', justifyContent: 'center' } },
createElement('iframe', {
srcDoc: html,
title: 'images in motion',
style: { width: '20rem', height: '20rem', border: 0, display: 'block' },
}),
)
}
Snippets use random Picsum URLs. The mosaic on this page uses a shuffled Unsplash set.
jsDelivr: pass https://cdn.jsdelivr.net/npm/images-in-motion as the second argument to createImagesInMotionWebViewHtml. Offline: copy dist/iife/images-in-motion.global.js into the app and pass that file URL.
Image URLs must be reachable from the WebView (https). file:// and blob: from the native side do not appear in the document unless you inject them.
The Web tab is the same createImagesInMotionWebViewHtml document in an iframe. A plain website can also use images-in-motion/react.