colorfy-software / react-native-modalfy

🥞 Modal citizen of React Native.
https://colorfy-software.gitbook.io/react-native-modalfy
MIT License
1.08k stars 44 forks source link

Animation conflict #28

Closed 1280103995 closed 3 years ago

1280103995 commented 3 years ago

Thanks for providing such a good library!

issues: When the next Modal enters the field, it will affect the exit animation of the previous Modal.

"dependencies": {
    "react": "16.13.1",
    "react-native": "0.63.0",
    "react-native-gesture-handler": "^1.8.0",
    "react-native-modalfy": "^2.1.0"
  }

Demo

import React, { PureComponent } from 'react';
import {View,Text,Button, Easing} from 'react-native'
import {createModalStack, ModalProvider, modalfy} from 'react-native-modalfy';
const {openModal} = modalfy();

class DefaultModal extends PureComponent {
  static modalOptions = {
    animateInConfig: {
      easing: Easing.linear,
      duration: 200,
    },
    animateOutConfig: {
      easing: Easing.out(Easing.circle),
      duration: 200,
    },
    backdropOpacity: 0.4,
    transitionOptions: animatedValue => ({
      opacity: animatedValue.interpolate({
        inputRange: [0, 1, 2],
        outputRange: [0, 1, 0],
      }),
      transform: [
        {
          scale: animatedValue.interpolate({
            inputRange: [0, 1, 1],
            outputRange: [0.4, 1, 1],
            extrapolate: 'clamp'
          }),
        },
      ],
    }),
  }

  render(){
    return(
      <View style={{
        alignSelf:'center',
        width: 300,
        height: 300,
        alignItems:'center',
        justifyContent:'center',
        paddingTop: 5,
        backgroundColor:'white'}}>
        <Text>{'Default'}</Text>
      </View>
    )
  }
}

class BottomModal extends PureComponent {
  static modalOptions = {
    transitionOptions: animatedValue => ({
      opacity: animatedValue.interpolate({
        inputRange: [0, 1, 2],
        outputRange: [0.5, 1, 0],
      }),
      transform: [
        {
          translateY: animatedValue.interpolate({
            inputRange: [0, 1, 2],
            outputRange: [200, 0, -200],
          }),
        },
        {
          scale: animatedValue.interpolate({
            inputRange: [0, 1, 2],
            outputRange: [1, 1, 0.1],
          }),
        },
      ],
    }),
    animateInConfig: {
      easing: Easing.bezier(0.42, -0.03, 0.27, 0.95),
      duration: 300,
    },
    animateOutConfig: {
      easing: Easing.bezier(0.42, -0.03, 0.27, 0.95),
      duration: 300,
    },
    backdropOpacity: 0.4,
    shouldAnimateOut: true,
  }

  render(){
    const {modal} = this.props;
    return(
      <View style={{
        alignSelf:'center',
        width: 300,
        height: 300,
        alignItems:'center',
        justifyContent:'center',
        paddingTop: 5,
        backgroundColor:'white'}}>
        <Text>{'After clicking \'show DefaultModal\', you can see that BottomModal is not executed according to the set animation. \n\n '}</Text>
        <Button title={'show DefaultModal'} onPress={()=>{
          modal.closeModal();
          modal.openModal('DefaultModal')
        }}/>
      </View>
    )
  }
}

const config = {
  DefaultModal,
  BottomModal
};
const ModalStack = createModalStack(config);

const App = () => {
  return (
    <ModalProvider stack={ModalStack}>
      <View style={{flex:1,alignItems:'center', justifyContent:'center'}}>
        <Text onPress={()=>{openModal('BottomModal')}}>{'show BottomModal'}</Text>
      </View>

    </ModalProvider>
  );
};

export default App;
CharlesMangwa commented 3 years ago

Hi @1280103995! The tl;dr; version is that you're closing the currentModal before opening 'DefaultModal'.

Modalfy initial purpose is to help you stack your different modals. So if you remove closeModal():

<Button
  title={'show DefaultModal'}
  onPress={() => {
-    modal.closeModal()
    modal.openModal('DefaultModal')
  }}
/>

things should work as expected and your 'DefaultModal' will appear on top of BottomModal.

However if you absolutely want to open 'DefaultModal' and not have 'BottomModal' opened once you'll close 'DefaultModal', you'd have to implement a setTimeout(). This is necessary has you have a 300ms closing animation in 'BottomModal':

animateOutConfig: {
  easing: Easing.bezier(0.42, -0.03, 0.27, 0.95),
  duration: 300,
},

and Modalfy removes item from the stack after the animation ran. So in your case, you have:

  1. Button 'show DefaultModal' is pressed
  2. 'BottomModal' closing animation starts - t+0ms
  3. openModal('DefaultModal') is called
  4. 'DefaultModal' is now the currentModal for Modalfy
  5. 'BottomModal' closing animation finishes - t+300ms
  6. Modalfy calls closeModal() under the hood on currentModal ('DefaultModal' instead of the desired 'BottomModal')

What you'd have to do is wait for the 300ms animation to be completed before opening 'DefaultModal'. By doing this you'd have both your in and out animations and 'BottomModal' won't be opened when you'll close 'DefaultModal':

<Button
  title={'show DefaultModal'}
  onPress={() => {
    modal.closeModal()
-    modal.openModal('DefaultModal')
+   setTimeout(() => modal.openModal('DefaultModal'), 300)
  }}
/>

Hope this helped and answered your question. I'm glad to read that Modalfy is helping you in your projects, hope to be able to see them soon in the stores! Feel free to reopen this issue if needed!