nandorojo/moti

AnimatePresence jittering/glitching on iOS

Closed this issue · 10 comments

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

starting from the fresh solito starter project and emulating one of our multi-step forms where we render the current step wrapped in AnimatePresence, cycling through the screens causes the view to jitter/glitch on iOS. this doesn't happen at all on web

Expected Behavior

no jittering when cycling between AnimatePresence's children

Steps To Reproduce

not sure what exactly is causing this issue, but the rough component hierarchy is like this:

  <AnimatePresence exitBeforeEnter>
            {parseInt(id) === 1 && (
              <Repro key="repro1" navDirection={navDirection} />
            )}
            {parseInt(id) === 2 && (
              <Repro key="repro2" navDirection={navDirection} />
            )}
            {parseInt(id) === 3 && (
              <Repro key="repro3" navDirection={navDirection} />
            )}
          </AnimatePresence>

where Repro looks like:

 <DripsyMotiView
      sx={{
        alignItems: 'center',
      }}
      from={{
        opacity: 0,
        translateX: navDirection === 'right' ? 100 : -100,
      }}
      animate={{
        opacity: 1,
        translateX: 0,
      }}
      exit={{
        opacity: 0,
        translateX: navDirection == 'right' ? -100 : 100,
      }}
      transition={{
        type: 'timing',
        duration: 500,
      }}
    >
      {Array.from({ length: 5 }).map((_, i) => (
        <ToggleButton key={i}>
          <Text>button {i}</Text>
        </ToggleButton>
      ))}
    </DripsyMotiView>

see the full code in the repro repository below (check app/features/user/detail-screen)

Versions

- Moti: 0.18.0
- Reanimated: 2.3.3
- React Native: 0.64.3

Screenshots

on web (desired behaviour)

on ios (jittering/glitching)

Reproduction

repro repository

If it’s useful, upgrading to expo 45 and latest react-native-reanimated fixes this issue, but it introduces some other iOS issues with moti which require us swapping out MotiView for Animated.View in places - not ideal!

Also having a similar issue on RN 0.67.4/moti 0.17.1/reanimated 2.4.1

would help if you upgrade to all latest versions / show a reproduction

For me, this seems to happen with entrance animations when navigating into the screen/opening the app (if that screen is the default). you can see the difference below. The code for the screen is pretty simple, here's a copy:

<MotiView style={{ flex: 1 }} from={{ opacity: 1 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>
            <AnimatePresence exitBeforeEnter>
                {introPage === 0 && 
                    <MotiView from={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} key={'introPage0'} style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
                        <MotiImage 
                            from={{ scale: 1 }} 
                            animate={{ scale: 1.1 }} 
                            transition={{
                                scale: {
                                    duration: 1500,
                                    easing: Easing.bezier(0, 0.55, 0.45, 1),
                                    type: 'timing'
                                }
                            }}
                            source={FirstPageBackground} 
                            style={{ position: 'absolute', resizeMode: 'cover', height: windowHeight, width: windowWidth }} 
                        />
                        <MotiView
                            from={{ translateY: -125 }}
                            animate={{ translateY: 0 }}
                            transition={{ 
                                duration: 500,
                                delay: 2000,
                                type: 'timing'
                            }}
                            style={{
                                position: 'absolute',
                                top: -97,
                                left: 22,
                                width: 212,
                                height: 222,
                                borderRadius: 111,
                                backgroundColor: '#6BE29B'
                            }}
                        />
                        <MotiView
                            from={{ translateY: -204 }}
                            animate={{ translateY: 0 }}
                            transition={{ 
                                duration: 500,
                                delay: 2500,
                                type: 'timing'
                            }}
                            style={{
                                position: 'absolute',
                                top: -18,
                                left: -106,
                                width: 212,
                                height: 222,
                                borderRadius: 111,
                                backgroundColor: '#6BE29B'
                            }}
                        />
                        <View style={{ top: windowHeight / 3 }}>
                            <MotiView
                                from={{ opacity: 0, translateY: 20 }}
                                animate={{ opacity: 1, translateY: 0 }}
                                transition={{ type: 'timing' }}
                                delay={1500}
                                style={styles.ViewD2}
                            >
                                <Text
                                    adjustsFontSizeToFit
                                    numberOfLines={1}
                                    style={[
                                        styles.headline1,
                                        { color: '#000', marginBottom: 20, marginTop: 5, fontSize: windowHeight * (20 / 844), maxWidth: windowWidth - 64 },
                                    ]}
                                >
                                    Sample Text
                                </Text>
                            </MotiView>
                            <MotiView
                                from={{ opacity: 0, translateY: 20 }}
                                animate={{ opacity: 1, translateY: 0 }}
                                transition={{ type: 'timing' }}
                                delay={3000}
                                style={styles.View_4v}
                            >
                                <TouchableOpacity
                                    onPress={() => setIntroPage(1)}
                                    style={[
                                        styles.ButtonSolidQB,
                                        { backgroundColor: '#6BE29B', marginTop: 0 },
                                    ]}
                                >
                                    <Text style={[styles.panelButtonText, { fontSize: 20 }]}>{'Get Started'}</Text>
                                </TouchableOpacity>
                            </MotiView>
                        </View>
                    </MotiView>
                }
            </AnimatePresence>
        </MotiView>

Desired behavior: https://user-images.githubusercontent.com/63302288/179582293-a997ed23-a308-4530-a2ca-596533bc54f9.mov

Actual behavior: https://user-images.githubusercontent.com/63302288/179582308-6384ef3f-7a31-4acc-9e9c-45043426c57c.MP4

thanks! can you make a snack?

https://snack.expo.dev/NzgunGHiO

seems to work fine on web

Just upgraded to moti 0.18 and now I'm getting an error:
null is not an object (evaluating dispatcher.useContext)
on a normal MotiView formatted as such:

<MotiView from={{ opacity: 0, scale: 0 }} animate={{ opacity: 1, scale: 1 }} transition={{ delay: 300 }} style={styles.sendMsgContainer}>
     <View />
</MotiView>

please see #189 (comment) for the solution to that

Found the solution to my issue. I was loading wayyy too much stuff when opening that screen. Moved the screen into a different component and the jitteriness went away

In general, I recommend not using AnimatePresence for deep component trees.