UNPKG

react-native-reanimated

Version:

More powerful alternative to Animated library for React Native.

270 lines (269 loc) • 9.27 kB
/* 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(); }