Created
November 10, 2023 17:24
-
-
Save tomsoderlund/99afdccc711ff8889310190305ee8ae4 to your computer and use it in GitHub Desktop.
React Native 2D animation and touch handling with expo-2d-context and expo-gl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useRef, useCallback, useEffect } from 'react' | |
import { GestureResponderEvent, PixelRatio } from 'react-native' | |
import { GLView } from 'expo-gl' | |
import Expo2DContext, { Expo2dContextOptions } from 'expo-2d-context' | |
const App = (): React.ReactElement => { | |
const ctxRef = useRef<Expo2DContext | null>(null) | |
const pixelRatio = PixelRatio.get() | |
let originPos = [0, 0] | |
// Inspired by @sergeymorkovkin: https://github.com/expo/expo/issues/11267#issuecomment-873630818 | |
const frameTimer = useRef<number>(0) | |
const frameCounter = useRef<number>(0) | |
const frameHandle = useRef<number | null>() | |
const processNextFrame = useCallback((time: number) => { | |
if (ctxRef.current !== null) { | |
update(time) | |
draw(frameCounter.current) | |
} | |
frameHandle.current = requestAnimationFrame(processNextFrame) | |
}, []) | |
useEffect(() => { | |
frameHandle.current = requestAnimationFrame(processNextFrame) | |
}, []) | |
const handleTouchPress = (e: GestureResponderEvent): void => { | |
console.log('handleTouchPress', Math.round(pixelRatio * e.nativeEvent.locationX), Math.round(pixelRatio * e.nativeEvent.locationY)) | |
originPos = [pixelRatio * e.nativeEvent.locationX, pixelRatio * e.nativeEvent.locationY] | |
} | |
const handleTouchRelease = (e: GestureResponderEvent): void => { | |
console.log('handleTouchRelease', Math.round(pixelRatio * e.nativeEvent.locationX), Math.round(pixelRatio * e.nativeEvent.locationY)) | |
} | |
const handleTouchMove = (e: GestureResponderEvent): void => { | |
console.log('handleTouchMove', Math.round(pixelRatio * e.nativeEvent.locationX), Math.round(pixelRatio * e.nativeEvent.locationY)) | |
} | |
const handleSetup = useCallback((gl: WebGLRenderingContext) => { | |
const ctx = new Expo2DContext(gl as unknown as number, undefined as unknown as Expo2dContextOptions) | |
ctxRef.current = ctx | |
originPos = [ctx.width / 2, ctx.height / 2] | |
}, []) | |
const update = (time: number): void => { | |
frameCounter.current += 1 | |
frameTimer.current = time | |
} | |
const draw = (frameNr: number): void => { | |
const ctx = ctxRef.current as Expo2DContext | |
// Clear canvas | |
ctx.clearRect(0, 0, ctx.width, ctx.height) | |
// Blue ball | |
const yPos = originPos[1] + ctx.height * 0.35 * Math.sin(frameNr / 30) | |
ctx.fillStyle = 'dodgerblue' | |
ctx.beginPath() | |
ctx.arc(originPos[0], yPos, 100, 0, 2 * Math.PI) | |
ctx.fill() | |
// Send drawing commands to GPU for rendering | |
ctx.flush() | |
} | |
return ( | |
<GLView | |
style={{ flex: 1 }} | |
onContextCreate={handleSetup} | |
onStartShouldSetResponder={() => true} | |
onResponderGrant={handleTouchPress} | |
onResponderRelease={handleTouchRelease} | |
onResponderMove={handleTouchMove} | |
/> | |
) | |
} | |
export default App |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment