How to Perform Inplace Animations in React Three Fiber - animation

I imported a 3D model from Mixamo with some animations and was wondering how to perform does animations in place?
The GLTF file I created from npx gltfjsx:
const { nodes, materials, animations } = useGLTF("/Mannequin.glb");
const { actions } = useAnimations(animations, heroRef);
return (
<>
<group ref={heroRef} dispose={null}>
<group rotation={[Math.PI / 2, 0, 0]} scale={0.01}>
<primitive object={nodes.mixamorig1Hips} />
<skinnedMesh
geometry={nodes.Ch36.geometry}
material={materials.Ch36_Body}
skeleton={nodes.Ch36.skeleton}
/>
</group>
{/* <gridHelper args={[25, 25]}/> */}
</group>
</>
);
}
useGLTF.preload("/Mannequin.glb");
For example, I would like the model to perform the run animation without changing locations (running on the spot) so that if I can perform user controls that translate the model and with the animation looping, it would look like they were actually running.
Are there any ways to do this? I have searched and could not find anything.

useEffect(() => {
console.log(actions) // find out the name of your action
actions.someAction.play()
});
This will make the animation play in place, if it was exported like that from mixamo, there is a setting.
Then you can put a around your mesh and make it move based on your control

I couldn't use console.log(actions) to see available actions as they were undefined and I had {} in console. My code was:
const { actions } = useAnimations(animations, group);
useEffect(() => {
console.log(actions);
}, [actions]);
So I digged inside into useAnimations source code and saw two things:
actions object is not being updated, only it's properties, so useEffect doesn't trigger when ref updates.
if ref.current is undefined (as for first render) properties are undefined. that's why I saw {} in console.
So I changed my code to:
const { actions } = useAnimations(animations, group);
useEffect(() => {
console.log(Object.keys(actions));
}, [actions]);
That's how I saw that my animation names were: ['Female_Idle', 'Female_Talk']
And
const { actions } = useAnimations(animations, group);
const action = actions['Female_Idle'];
useEffect(() => {
if (action) action.play();
}, [action]);
In order to play action, when ref updates. That did the trick.

Related

React-Navigation v4 pass state from child to parent on back press

I need to pass state from my child screen to the parent screen . I am having difficulties doing this. I am letting the user press a button to navigate to the child screen. After filling info in the child screen I am trying to pass the value back to the parent screen with props.navigation.goBack() Can someone help me out with this.
I am using react navigation v4
Parent screen:
const Parent = (props) => {
const [day, setDay] = useState("");
return (
<TouchableOpacity
onPress={() =>props.navigation.navigate({ routeName: 'Child' })}>
</TouchableOpacity>
);
};
Child screen (I want to pass the day state back to the parent)
const Child = props => {
const [day, setDay] = useState("");
return (
<View style={styles.container}>
<TextInput onChange={(text)=> setDay(text)}/>
<Button onPress={()=>props.navigation.goBack()}/>
</View>
);
};
If it is not possible to use the normal navigation way maybe try to build your own back function and pass params with it.
Take a look at this maybe:
goBack() {
const { navigation } = this.props;
navigation.goBack();
navigation.setParam({ day: dayData});
}
then the call would be :
<Button onPress={()=>this.goBack()}/>
you can get the param with :
this.props.navigation.getParam("day")
try it somehow like this - if it does not work try the calls with this.props.navigation...
or only with navigation.goBack() and so on because I am not sure wich syntax will work for you.

How to specify custom transitions for specific routes, not screens (v5)

Question regarding react-navigation v5.
In previous versions, we were able to specify custom transition for specific routes, not screens, by doing the following inside a StackNavigator:
transitionConfig: () => ({
screenInterpolator: sceneProps => {
const {scenes, scene} = sceneProps;
const prevRoute = scenes[0].route.routeName === 'Route A';
// If prev route is A, and then current route is B, then we do a specific transition
if (prevRoute && scene.route.routeName === 'Route B') {
return StackViewStyleInterpolator.forVertical(sceneProps);
}
// Otherwise default to normal transition
return StackViewStyleInterpolator.forHorizontal(sceneProps);
},
}),
Now, I'm trying to achieve the same for react-navigation v5. I know I'm able to specify a custom animation per screen by doing something like:
<Stack.Screen name="Route B" component={RouteB} options={{ cardStyleInterpolator: CardStyleInterpolators.forVerticalIOS }} />
The problem is that I don't want this transition applied every time it is navigated to RouteB, ONLY when the previous route is RouteA, I want this transition applied, just like the previous code block above.
Couldn't find any example in the docs so would appreciate some help in migrating the code over to v5.
Something like this should work:
options={({ navigation, route }) => {
const state = navigation.dangerouslyGetState();
const index = state.routes.indexOf(route);
const previousRoute = state.routes[index - 1];
if (previousRoute?.name === 'RouteA') {
return {
cardStyleInterpolator: CardStyleInterpolators.forVerticalIOS,
gestureDirection: 'vertical',
};
} else {
return {};
}
}}

Complex navigation in React Native using react-navigation and Redux

I have the following navigation structure in my React Native app:
StackNavigator configured with 3 routes:
Splash screen (React Component)
StackNavigator for my login flow
DrawerNavigator for my core app screens.
The DrawerNavigator has some dynamic multiple routes, but also one static route which is another StackNavigator.
Everything seems to be working as expected:
The store is being updated accordingly.
Navigation between screen works.
Go back between screen works when configured within each component, with the following command:
this.props.navigation.goBack();
My question is - is there a way for me to handle back button on Android globally? Currently when I click on the back button, nothing happens (due to the fact I'm using Redux). Should I handle the back button in each component or is there a way of doing it using Redux?
A bit late, but there is a way to handle this with redux. In your index.js file where you create your store you can make export a class and add a componentWillMount call to handle dispatching a call to your redux actions. Just remember to import the actions you need above.
const store = configureStore();
export default class Index extends Component {
componentWillMount = () => {
BackHandler.addEventListener('hardwareBackPress', () => {
const { nav: { routes } } = store.getState();
const currentRouteName = routes[routes.length-1].routeName;
if (currentRouteName === 'EditCoupleProfile') {
store.dispatch(editCoupleActions.navigateBack())
} else if ( currentRouteName === 'EditInterests' ) {
store.dispatch(interestsActions.navigateBack())
} else {
store.dispatch(popFromStack());
}
return true;
})
};
componentWillUnmount = () => {
BackHandler.removeEventListener('hardwareBackPress');
};
render() {
return (
<Provider store={store}>
<AppWithNavigation />
</Provider>
);
}
}

react-native: disable animations

We are using Animated and react-native-animatable quite heavily and starting to notice slowness on some old devices. All animations set useNativeDriver which makes us believe that we may have a few too many animations.
Is there a way to overwrite the Animated prototype to completely disable animations? I looked into this and it didn't seem simple.
Another option I'm considering is to leave my fade animations in but set the initial value in the constructor to the final value. This approach definitely doesn't show any animations but would it also bypass the animation in the native bridge as the value isn't changing?
class Item extends Component {
constructor(props) {
super(props);
this.state = {
opacity: 1 // Notice how this is set to 1
}
}
componentDidMount() {
setTimeout(() => {
this.setState({opacity: 1})
}, 1000)
}
render() {
return (
<Animatable.View style={{opacity}} easing='ease-in' transition='opacity' duration={500} useNativeDriver={true} />
)
}
}
Just create a wrapping component for it and use that instead of Animated.View
export default const AnimatedViewWrapper = (props) => {
if (/* slow device check */) {
return React.createElement(View, props)
} else {
return React.createElement(Animated.View, props)
}
}
You might need to filter the props you receive because View does not have many of the props that Animated.View has. You can get them through View.propTypes. You might need to do this only if __DEV__ is true as propTypes are stripped out in production builds

Which component should control the loading state of a lower component?

Let's say I have these components:
Translator
TranslationList
Translator determines translation context, has translate function.
TranslationList must show these "visual states": loading, result list, no results.
The Translator moves around the page (one instance): on focusing an input, it moves "below" it and gives a dropdown with suggestion.
So each time it moves, it has to:
Show that it's loading translations
Show translation list or no results message.
So my question is:
Which component should control the "loading" visual state?
If the Translator component controls it, it has to pass loading=true translations=[] as props to Translation list. Then later it has to rerender it again with new props loading=false translations=[...]. This seems a bit counter-intuitive, because loading feels like the state of the TranslationList component.
If we the TranslationList component has loading state, then it also has to have a way to translate things, meaning that I have to pass translate function as prop. I would then hold translations and loading as state. This all gets a bit messy, since it must now also receive string to translate, context.
I also don't want to have separate components for loading message, no results message. I'd rather keep these inside the TranslationList, because these 3 share that same wrapper <div class="list-group"></div>
Perhaps there should be one more Component in between these two components, responsible only for fetching translation data?
Translator component should control the loading state of a lower component list component. hold the loading and translating logic but with help by wrapping it in a high order component where you should put most of the logic. link for HOC https://www.youtube.com/watch?v=ymJOm5jY1tQ.
const translateSelected = wrappedComponent =>
//return Translator component
class extends React.Component {
state = {translatedText: [], loading:true}
componentDidMount(){
fetch("text to translate")
.then(transText => this.setState({translatedText: transText, loading: false}))
}
render() {
const {translatedText} = this.state
return <WrappedComponent {..this.props} {...translatedText}
}
}
const Translator_HOC = translateSelected(Translator);
You could introduce a Higher Order Component to control the switching of the loading state and the TranslationList. That way you separate the loading display away from your TranslationList as being it's concern. This also allows you to use the HOC in other areas.
The Translator can act as "container" component which does the data fetching/passing.
For example:
// The Loadable HOC
function Loadable(WrappedComponent) {
return function LoadableComponent({ loaded, ...otherProps }) {
return loaded
? <WrappedComponent {...otherProps} />
: <div>Loading...</div>
}
}
// Translation list doesn't need to know about "loaded" prop
function TranslationList({ translations }) {
return (
<ul>
{
translations.map((translation, index) =>
<li key={index}>{translation}</li>
)
}
</ul>
)
}
// We create our new composed component here.
const LoadableTranslationList = Loadable(TranslationList)
class Translator extends React.Component {
state = {
loaded: false,
translations: []
}
componentDidMount() {
// Let's simulate a data fetch, typically you are going to access
// a prop like this.props.textToTranslate and then pass that to
// an API or redux action to fetch the respective translations.
setTimeout(() => {
this.setState({
loaded: true,
translations: [ 'Bonjour', 'Goddag', 'Hola' ]
});
}, 2000);
}
render() {
const { loaded, translations } = this.state;
return (
<div>
<h3>Translations for "{this.props.textToTranslate}"</h3>
<LoadableTranslationList loaded={loaded} translations={translations} />
</div>
)
}
}
ReactDOM.render(<Translate textToTranslate="Hello" />)
Running example here: http://www.webpackbin.com/NyQnWe54W

Resources