react-native-reanimated
Version:
More powerful alternative to Animated library for React Native.
270 lines (269 loc) • 9.27 kB
JavaScript
/* global _WORKLET _getCurrentTime _frameTimestamp _eventTimestamp, _setGlobalConsole */
import NativeReanimatedModule from './NativeReanimated';
import { Platform } from 'react-native';
import { nativeShouldBeMock, shouldBeUseWeb, isWeb } from './PlatformChecker';
if (global._setGlobalConsole === undefined) {
// it can happen when Reanimated plugin wasn't added, but the user uses the only API from version 1
global._setGlobalConsole = () => {
// noop
};
}
const testWorklet = () => {
'worklet';
};
const throwUninitializedReanimatedException = () => {
throw new Error("Failed to initialize react-native-reanimated library, make sure you followed installation steps here: https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation/ \n1) Make sure reanimated's babel plugin is installed in your babel.config.js (you should have 'react-native-reanimated/plugin' listed there - also see the above link for details) \n2) Make sure you reset build cache after updating the config, run: yarn start --reset-cache");
};
export const checkPluginState = (throwError = true) => {
if (!testWorklet.__workletHash && !shouldBeUseWeb()) {
if (throwError) {
throwUninitializedReanimatedException();
}
return false;
}
return true;
};
export const isConfigured = (throwError = false) => {
return checkPluginState(throwError);
};
export const isConfiguredCheck = () => {
checkPluginState(true);
};
function pushFrame(frame) {
NativeReanimatedModule.pushFrame(frame);
}
export function requestFrame(frame) {
'worklet';
if (NativeReanimatedModule.native) {
requestAnimationFrame(frame);
}
else {
pushFrame(frame);
}
}
global._WORKLET = false;
global._log = function (s) {
console.log(s);
};
export function runOnUI(worklet) {
return makeShareable(worklet);
}
export function makeShareable(value) {
isConfiguredCheck();
return NativeReanimatedModule.makeShareable(value);
}
export function getViewProp(viewTag, propName) {
return new Promise((resolve, reject) => {
return NativeReanimatedModule.getViewProp(viewTag, propName, (result) => {
if (typeof result === 'string' && result.substr(0, 6) === 'error:') {
reject(result);
}
else {
resolve(result);
}
});
});
}
let _getTimestamp;
if (nativeShouldBeMock()) {
_getTimestamp = () => {
return NativeReanimatedModule.getTimestamp();
};
}
else {
_getTimestamp = () => {
'worklet';
if (_frameTimestamp) {
return _frameTimestamp;
}
if (_eventTimestamp) {
return _eventTimestamp;
}
return _getCurrentTime();
};
}
export function getTimestamp() {
'worklet';
if (Platform.OS === 'web') {
return NativeReanimatedModule.getTimestamp();
}
return _getTimestamp();
}
function workletValueSetter(value) {
'worklet';
const previousAnimation = this._animation;
if (previousAnimation) {
previousAnimation.cancelled = true;
this._animation = null;
}
if (typeof value === 'function' ||
(value !== null &&
typeof value === 'object' &&
value.onFrame !== undefined)) {
const animation = typeof value === 'function'
? value()
: value;
// prevent setting again to the same value
// and triggering the mappers that treat this value as an input
// this happens when the animation's target value(stored in animation.current until animation.onStart is called) is set to the same value as a current one(this._value)
// built in animations that are not higher order(withTiming, withSpring) hold target value in .current
if (this._value === animation.current && !animation.isHigherOrder) {
animation.callback && animation.callback(true);
return;
}
// animated set
const initializeAnimation = (timestamp) => {
animation.onStart(animation, this.value, timestamp, previousAnimation);
};
initializeAnimation(getTimestamp());
const step = (timestamp) => {
if (animation.cancelled) {
animation.callback && animation.callback(false /* finished */);
return;
}
const finished = animation.onFrame(animation, timestamp);
animation.finished = true;
animation.timestamp = timestamp;
this._value = animation.current;
if (finished) {
animation.callback && animation.callback(true /* finished */);
}
else {
requestAnimationFrame(step);
}
};
this._animation = animation;
if (_frameTimestamp) {
// frame
step(_frameTimestamp);
}
else {
requestAnimationFrame(step);
}
}
else {
// prevent setting again to the same value
// and triggering the mappers that treat this value as an input
if (this._value === value) {
return;
}
this._value = value;
}
}
// We cannot use pushFrame
// so we use own implementation for js
function workletValueSetterJS(value) {
const previousAnimation = this._animation;
if (previousAnimation) {
previousAnimation.cancelled = true;
this._animation = null;
}
if (typeof value === 'function' ||
(value !== null &&
typeof value === 'object' &&
value.onFrame)) {
// animated set
const animation = typeof value === 'function'
? value()
: value;
let initializeAnimation = (timestamp) => {
animation.onStart(animation, this.value, timestamp, previousAnimation);
};
const step = (timestamp) => {
if (animation.cancelled) {
animation.callback && animation.callback(false /* finished */);
return;
}
if (initializeAnimation) {
initializeAnimation(timestamp);
initializeAnimation = null; // prevent closure from keeping ref to previous animation
}
const finished = animation.onFrame(animation, timestamp);
animation.timestamp = timestamp;
this._setValue && this._setValue(animation.current);
if (finished) {
animation.callback && animation.callback(true /* finished */);
}
else {
requestFrame(step);
}
};
this._animation = animation;
requestFrame(step);
}
else {
this._setValue && this._setValue(value);
}
}
export function makeMutable(value) {
isConfiguredCheck();
return NativeReanimatedModule.makeMutable(value);
}
export function makeRemote(object = {}) {
isConfiguredCheck();
return NativeReanimatedModule.makeRemote(object);
}
export function startMapper(mapper, inputs = [], outputs = [], updater = () => {
// noop
}, viewDescriptors = []) {
isConfiguredCheck();
return NativeReanimatedModule.startMapper(mapper, inputs, outputs, updater, viewDescriptors);
}
export function stopMapper(mapperId) {
NativeReanimatedModule.stopMapper(mapperId);
}
export function runOnJS(fun) {
'worklet';
if (!_WORKLET) {
return fun;
}
if (!fun.__callAsync) {
throw new Error("Attempting to call runOnJS with an object that is not a host function. Using runOnJS is only possible with methods that are defined on the main React-Native Javascript thread and that aren't marked as worklets");
}
else {
return fun.__callAsync;
}
}
NativeReanimatedModule.installCoreFunctions(NativeReanimatedModule.native
? workletValueSetter
: workletValueSetterJS);
if (!isWeb() && isConfigured()) {
const capturableConsole = console;
runOnUI(() => {
'worklet';
const console = {
debug: runOnJS(capturableConsole.debug),
log: runOnJS(capturableConsole.log),
warn: runOnJS(capturableConsole.warn),
error: runOnJS(capturableConsole.error),
info: runOnJS(capturableConsole.info),
};
_setGlobalConsole(console);
})();
}
let featuresConfig = {
enableLayoutAnimations: false,
setByUser: false,
};
export function enableLayoutAnimations(flag, isCallByUser = true) {
if (isCallByUser) {
featuresConfig = {
enableLayoutAnimations: flag,
setByUser: true,
};
NativeReanimatedModule.enableLayoutAnimations(flag);
}
else if (!featuresConfig.setByUser &&
featuresConfig.enableLayoutAnimations !== flag) {
featuresConfig.enableLayoutAnimations = flag;
NativeReanimatedModule.enableLayoutAnimations(flag);
}
}
export function configureProps(uiProps, nativeProps) {
if (!nativeShouldBeMock()) {
NativeReanimatedModule.configureProps(uiProps, nativeProps);
}
}
export function jestResetJsReanimatedModule() {
NativeReanimatedModule.jestResetModule();
}