react-native-reanimated
Version:
More powerful alternative to Animated library for React Native.
275 lines (247 loc) • 7.69 kB
text/typescript
import { isChromeDebugger, isJest, isWeb } from '../PlatformChecker';
import type {
ShareableRef,
ShareableSyncDataHolderRef,
Value3D,
ValueRotation,
} from '../commonTypes';
import { SensorType } from '../commonTypes';
import type { WebSensor } from './WebSensor';
export default class JSReanimated {
native = false;
nextSensorId = 0;
sensors = new Map<number, WebSensor>();
platform?: Platform = undefined;
makeShareableClone<T>(): ShareableRef<T> {
throw new Error(
'[Reanimated] makeShareableClone should never be called in JSReanimated.'
);
}
installCoreFunctions(
_callGuard: <T extends Array<unknown>, U>(
fn: (...args: T) => U,
...args: T
) => void,
_valueUnpacker: <T>(value: T) => T
): void {
// noop
}
scheduleOnUI<T>(worklet: ShareableRef<T>) {
// @ts-ignore web implementation has still not been updated after the rewrite, this will be addressed once the web implementation updates are ready
requestAnimationFrame(worklet);
}
registerEventHandler<T>(
_eventHash: string,
_eventHandler: ShareableRef<T>
): number {
// noop
return -1;
}
unregisterEventHandler(_: number): void {
// noop
}
enableLayoutAnimations() {
if (isWeb()) {
console.warn(
'[Reanimated] Layout Animations are not supported on web yet.'
);
} else if (isChromeDebugger()) {
console.warn(
'[Reanimated] Layout Animations are no-ops when using Chrome Debugger.'
);
} else if (isJest()) {
console.warn(
'[Reanimated] Layout Animations are no-ops when using Jest.'
);
} else {
console.warn(
'[Reanimated] Layout Animations are not supported on this configuration.'
);
}
}
configureLayoutAnimation() {
// no-op
}
registerSensor(
sensorType: SensorType,
interval: number,
_iosReferenceFrame: number,
eventHandler: ShareableRef<(data: Value3D | ValueRotation) => void>
): number {
if (this.platform === undefined) {
this.detectPlatform();
}
if (!(this.getSensorName(sensorType) in window)) {
// https://w3c.github.io/sensors/#secure-context
console.warn(
'[Reanimated] Sensor is not available.' +
(isWeb() && location.protocol !== 'https:'
? ' Make sure you use secure origin with `npx expo start --web --https`.'
: '') +
(this.platform === Platform.WEB_IOS
? ' For iOS web, you will also have to also grant permission in the browser: https://dev.to/li/how-to-requestpermission-for-devicemotion-and-deviceorientation-events-in-ios-13-46g2.'
: '')
);
return -1;
}
if (this.platform === undefined) {
this.detectPlatform();
}
const sensor: WebSensor = this.initializeSensor(sensorType, interval);
let callback;
if (sensorType === SensorType.ROTATION) {
callback = () => {
let [qw, qx, qy, qz] = sensor.quaternion;
// Android sensors have a different coordinate system than iOS
if (this.platform === Platform.WEB_ANDROID) {
[qy, qz] = [qz, -qy];
}
// reference: https://stackoverflow.com/questions/5782658/extracting-yaw-from-a-quaternion
const yaw = Math.atan2(
2.0 * (qy * qz + qw * qx),
qw * qw - qx * qx - qy * qy + qz * qz
);
const pitch = Math.sin(-2.0 * (qx * qz - qw * qy));
const roll = Math.atan2(
2.0 * (qx * qy + qw * qz),
qw * qw + qx * qx - qy * qy - qz * qz
);
// TODO TYPESCRIPT on web ShareableRef is the value itself so we call it directly
(eventHandler as any)({
qw,
qx,
qy,
qz,
yaw,
pitch,
roll,
interfaceOrientation: 0,
});
};
} else {
callback = () => {
let { x, y, z } = sensor;
[x, y, z] =
this.platform === Platform.WEB_ANDROID ? [-x, -y, -z] : [x, y, z];
// TODO TYPESCRIPT on web ShareableRef is the value itself so we call it directly
(eventHandler as any)({ x, y, z, interfaceOrientation: 0 });
};
}
sensor.addEventListener('reading', callback);
sensor.start();
this.sensors.set(this.nextSensorId, sensor);
return this.nextSensorId++;
}
unregisterSensor(id: number): void {
const sensor: WebSensor | undefined = this.sensors.get(id);
if (sensor !== undefined) {
sensor.stop();
this.sensors.delete(id);
}
}
subscribeForKeyboardEvents(_: ShareableRef<number>): number {
if (isWeb()) {
console.warn(
'[Reanimated] useAnimatedKeyboard is not available on web yet.'
);
} else if (isChromeDebugger()) {
console.warn(
'[Reanimated] useAnimatedKeyboard is not available when using Chrome Debugger.'
);
} else if (isJest()) {
console.warn(
'[Reanimated] useAnimatedKeyboard is not available when using Jest.'
);
} else {
console.warn(
'[Reanimated] useAnimatedKeyboard is not available on this configuration.'
);
}
return -1;
}
unsubscribeFromKeyboardEvents(_: number): void {
// noop
}
initializeSensor(sensorType: SensorType, interval: number): WebSensor {
const config =
interval <= 0
? { referenceFrame: 'device' }
: { frequency: 1000 / interval };
switch (sensorType) {
case SensorType.ACCELEROMETER:
return new window.Accelerometer(config);
case SensorType.GYROSCOPE:
return new window.Gyroscope(config);
case SensorType.GRAVITY:
return new window.GravitySensor(config);
case SensorType.MAGNETIC_FIELD:
return new window.Magnetometer(config);
case SensorType.ROTATION:
return new window.AbsoluteOrientationSensor(config);
}
}
getSensorName(sensorType: SensorType): string {
switch (sensorType) {
case SensorType.ACCELEROMETER:
return 'Accelerometer';
case SensorType.GRAVITY:
return 'GravitySensor';
case SensorType.GYROSCOPE:
return 'Gyroscope';
case SensorType.MAGNETIC_FIELD:
return 'Magnetometer';
case SensorType.ROTATION:
return 'AbsoluteOrientationSensor';
}
}
detectPlatform() {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
if (userAgent === undefined) {
this.platform = Platform.UNKNOWN;
} else if (/iPad|iPhone|iPod/.test(userAgent)) {
this.platform = Platform.WEB_IOS;
} else if (/android/i.test(userAgent)) {
this.platform = Platform.WEB_ANDROID;
} else {
this.platform = Platform.WEB;
}
}
makeSynchronizedDataHolder<T>(
_valueRef: ShareableRef<T>
): ShareableSyncDataHolderRef<T> {
throw new Error(
'[Reanimated] makeSynchronizedDataHolder is not available in JSReanimated.'
);
}
getDataSynchronously<T>(_ref: ShareableSyncDataHolderRef<T>): T {
throw new Error(
'[Reanimated] getDataSynchronously is not available in JSReanimated.'
);
}
getViewProp<T>(
_viewTag: string,
_propName: string,
_callback?: (result: T) => void
): Promise<T> {
throw new Error(
'[Reanimated] getViewProp is not available in JSReanimated.'
);
}
configureProps() {
throw new Error(
'[Reanimated] configureProps is not available in JSReanimated.'
);
}
}
enum Platform {
WEB_IOS = 'web iOS',
WEB_ANDROID = 'web Android',
WEB = 'web',
UNKNOWN = 'unknown',
}
declare global {
interface Navigator {
userAgent?: string;
vendor?: string;
}
}