Skip to content

Instantly share code, notes, and snippets.

@tomsoderlund
Created November 10, 2023 17:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomsoderlund/99afdccc711ff8889310190305ee8ae4 to your computer and use it in GitHub Desktop.
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
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