Skip to content

React Native

On React Native, Mimic reads frames from VisionCamera 5 on the camera thread, runs the native face tracker through a Nitro module, and judges gestures on the JS thread. Your component gets a hook and a camera view.

Version
React Native 0.79 or later, New Architecture (tested on 0.87)
react-native-vision-camera 5.x
react-native-nitro-modules 0.37 or later
react-native-worklets 0.13 or later
iOS / Android iOS 15+, Android 7 (API 24)+ with a front camera

Expo works with a development build (expo prebuild), not Expo Go, since Mimic ships native code.

Configure access first, then:

Terminal window
npm install @vidrip/mimic-react-native @vidrip/mimic-landmarker @vidrip/mimic-core \
react-native-vision-camera react-native-nitro-modules react-native-worklets
cd ios && pod install

@vidrip/mimic-landmarker is the native module and carries the face model, so there’s nothing to download at runtime.

Add a camera usage string to ios/<App>/Info.plist:

<key>NSCameraUsageDescription</key>
<string>Uses the front camera to read your facial gestures. Nothing leaves your device.</string>

and the permission to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />

Ask for it at runtime with VisionCamera’s useCameraPermission() before showing the camera.

import { useCameraPermission } from 'react-native-vision-camera';
import { MimicCamera, useMimic } from '@vidrip/mimic-react-native';
export function Game() {
const { hasPermission, requestPermission } = useCameraPermission();
const face = useMimic({
on: {
'blink': (e) => { if (e.phase === 'start') fire(); },
'head.left': (e) => { if (e.phase === 'start') moveLeft(); },
'smile': (e) => setGrin(e.phase === 'start' ? e.strength : 0),
},
});
if (!hasPermission) return <Button title="Allow camera" onPress={requestPermission} />;
return (
<>
<MimicCamera mimic={face} style={StyleSheet.absoluteFill} />
{!face.profile && <Button title="Calibrate" onPress={() => face.calibrate()} />}
</>
);
}

Handlers run on the JS thread. useMimic re-renders only a few times a second (for faceFound, fps and calibration progress), never per frame, so binding gestures doesn’t make your screen re-render at camera rate.

face.calibrate() resolves with { profile, status }. While it runs, face.calibration.progress and face.calibration.quality drive your prompt UI. Persist the profile (for example with onProfile) and pass it back as profile to skip calibration next time:

const face = useMimic({ profile: saved, onProfile: (p) => storage.set('mimic', JSON.stringify(p)), on });

To make calibration wait until the face is inside your on-screen guide, pass framing: (centre) => boolean; the centre is where the eyes sit in the frame (0 to 1).

  • blendshapes: true runs the expression network too. It’s needed for eyes.up and expression.match, and costs a few milliseconds per frame.
  • gestures: [...] arms only the gestures you use.
  • landmarks: 'always' keeps the full 478-point mesh on every sample, for your own effects. The default sends only the points the gestures need.