Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LayoutAnimations entering and exiting not working #5395

Closed
mrousavy opened this issue Nov 22, 2023 · 8 comments · Fixed by #5644
Closed

LayoutAnimations entering and exiting not working #5395

mrousavy opened this issue Nov 22, 2023 · 8 comments · Fixed by #5644
Labels
Area: Layout Animations Platform: Android This issue is specific to Android Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snippet of code, snack or repo is provided

Comments

@mrousavy
Copy link
Contributor

mrousavy commented Nov 22, 2023

Description

Hey yall,

I tried playing around with LayoutAnimations and I love the idea - they're so simple to implement, amazing work!

Screen_Recording_2023-11-14_at_17.36.31.mov

I did notice that there are a few bugs though.

I created a repro here that showcases those bugs: #5394

1. Exiting not running

The exiting animation is not running in the given example.

2. Custom useAnimatedStyle not running on first mount

On the first mount, the useAnimatedStyle for the second card is not properly applied. I think this is a race condition where the LayoutAnimation takes precedence over the useAnimatedStyle.

3. Some items not showing

Sometimes the items just don't show up.

4. layout instead of useAnimatedStyle does not work

When using layout instead of manually using useAnimatedStyle it will not consider properties in the transform style.

This is how I animate the slide & rotate to the right now:

const rotation = useDerivedValue(() => withSpring(index * 15), [index]);

const animatedStyle = useAnimatedStyle(() => {
  return {
    zIndex: -index,
    backgroundColor: item.color,
    transform: [
      {
        translateX: interpolate(rotation.value, [0, 100], [0, 100]),
      },
      {
        translateY: interpolate(rotation.value, [0, 100], [0, -30]),
      },
      {
        scale: interpolate(rotation.value, [0, 100], [1, 0.7]),
      },
      {
        rotate: `${rotation.value}deg`,
      },
    ],
  };
}, [rotation, index, item]);

return (
  <Animated.View
    entering={Entering}
    exiting={Exiting}
    style={[styles.item, animatedStyle]}
  />
);

This is how I want to do it:

const style = useMemo(() => {
  return {
    zIndex: -index,
    backgroundColor: item.color,
    transform: [
      {
        translateX: x,
      },
      {
        translateY: y,
      },
      {
        scale: scale,
      },
      {
        rotate: `${rotation}deg`,
      },
    ],
  };
}, [rotation, index, item]);

return (
  <Animated.View
    entering={Entering}
    exiting={Exiting}
    layout={Move} // <-- just a default Layout.springify() animation that should animate the props in transform: [...]
    style={[styles.item, style]}
  />
);

Let me know if you need more details!

Steps to reproduce

#5394 <-- open the new page

Snack or a link to a repository

#5394

Reanimated version

3.5.0 (main)

React Native version

0.72.6

Platforms

Android, iOS

JavaScript runtime

Hermes

Workflow

React Native

Architecture

Paper (Old Architecture)

Build type

Debug mode

Device

iOS simulator

Device model

iPhone 15 Pro Simulator

Acknowledgements

Yes

@mrousavy mrousavy added the Needs review Issue is ready to be reviewed by a maintainer label Nov 22, 2023
@github-actions github-actions bot added the Missing repro This issue need minimum repro scenario label Nov 22, 2023
Copy link

Hey! 👋

The issue doesn't seem to contain a minimal reproduction.

Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?

@github-actions github-actions bot added Platform: Android This issue is specific to Android Platform: iOS This issue is specific to iOS labels Nov 22, 2023
@kacperkapusciak kacperkapusciak added Repro provided A reproduction with a snippet of code, snack or repo is provided and removed Missing repro This issue need minimum repro scenario labels Nov 24, 2023
@Iannery
Copy link

Iannery commented Dec 1, 2023

Same error here. It was working with V2, but after upgrading to V3 it sometimes worked, but not consistently. I'm using it on a series of screens that have the same animation, and sometimes the item just fades in partially, other times it just does nothing and gives me a transparent screen. I had this issue on both Android and IOS.

<Animated.View
  style={{ flex: 1 }}
  layout={Layout.duration(300).delay(300)}
  entering={FadeInUp}
  exiting={FadeOut} 
/>

One thing I noticed was that it did render the component since the buttons I had inside the view still worked, and its children were also selectable in the element inspector.

@tomekzaw tomekzaw added Area: Layout Animations and removed Needs review Issue is ready to be reviewed by a maintainer labels Dec 7, 2023
@tomekzaw
Copy link
Member

Hey @mrousavy, thanks again for reporting this issue.

1. Exiting not running

The exiting animation is running (you can substitute ZoomOut with FadeOut and it will work correctly), but does not account for transforms. In this case, it's overlapped by other cards while exiting. If you change the opacity of the cards, you can see exactly what happens:

1.mov

2. Custom useAnimatedStyle not running on first mount

Not really sure what this means, could you provide more details on what's the actual and expected behavior? One problem I can clearly see is that during the first render the position of the cards does not account for header height; this is a regression caused by some changes in react-native-screens, here's how it looks like:

Without screen Inside screen
without.mov
inside.mov

3. Some items not showing

I was able to reproduce this problem but it looks like it's caused by incorrect implementation of getRandomColor in your example. Sometimes it returns an invalid hex color (e.g. #1d7ea). Here's a fix that solved it for me:

 function getRandomColor(): string {
-  return '#' + Math.floor(Math.random() * 16777215).toString(16);
+  return '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
 }

4. layout instead of useAnimatedStyle does not work

That's true, currently entering, layout and exiting animations simply overwrite existing transforms. It would be really cool if layout supported animating transforms as well. The current behavior is the following: if a component changes its Yoga layout (position OR size), we run the animation. Technically, transforms don't affect the layout of the component so in this case the layout does not change at all.

@mrousavy
Copy link
Contributor Author

Thanks for the detailed insights Tomasz, really interesting!

Regarding "2. Custom useAnimatedStyle not running on first mount":

I am not sure what the issue there was as I am not able to reproduce it anymore - but in my app the back card was not rotated to the right when the app first starts, it was just behind the first card - no rotations applied. So it was invisible to the user.

I'll try to reproduce that again when I get the chance!

@computerjazz
Copy link

computerjazz commented Dec 18, 2023

Hey all, I've opened previous issues with repros for some of these, hopefully they provide some more insight:

@henrymoulton
Copy link

henrymoulton commented Jan 9, 2024

Currently chasing down a race condition with REA 3.6.1 and Bottom Sheet 4.6.0, that might be connected to @computerjazz but if we downgrade to REA 2 the issue doesn't seem to be there which isn't consistent with the bug report in #3296 I think.

@mrousavy was useAnimatedStyle bug happening across v2 and v3?

@mohamine18
Copy link

Problem:
I encountered a similar issue where combining two animated styles using the style={[animatedStyle1, animatedStyle2]} syntax worked as expected on Android but not on iOS. One style was for sliding a component, and the other was for animating the backgroundColor of the same component.

Solution Attempt:
To address the issue on iOS, I found a workaround by wrapping each animated style in a separate Animated.View component. This involved using two separate views with their respective styles: <Animated.View style={animatedStyle1}><Animated.View style={animatedStyle2}>...</Animated.View></Animated.View>. While this resolved the problem on iOS, it feels like a less-than-ideal solution.

Suggested Improvement:
It would be great if the combined style syntax style={[animatedStyle1, animatedStyle2]} could work consistently across both Android and iOS platforms. This would enhance the developer experience by providing a more straightforward and cross-platform solution.

Additional Information:

React Native Version: 0.72.6
React Native Reanimated: 3.3.0
Expo SDK: 49
Platform: iOS 17.2

github-merge-queue bot pushed a commit that referenced this issue Feb 20, 2024
## Summary
In this PR
#4969 I
didn't include neither animatedStyles nor styles nested in arrays.
That's why the problem wasn't reported in this example:
#5395

closes #5395 

|Before|After|
|---|---|
|<img width="387" alt="image"
src="https://github.com/software-mansion/react-native-reanimated/assets/56199675/52c1de46-49d8-458c-ac4b-659bbcbca531">|<img
width="387" alt="image"
src="https://github.com/software-mansion/react-native-reanimated/assets/56199675/d12eb7d6-b409-41b8-873b-bc8cbc46e258">|

## Test plan

<details><summary>Code</summary>

```jsx
import Animated, {
  useAnimatedStyle,
  SlideInDown,
  ZoomOut,
  useDerivedValue,
  withSpring,
  interpolate,
} from 'react-native-reanimated';
import { StyleSheet, View } from 'react-native';

import React, { useEffect, useState } from 'react';

interface Item {
  color: string;
}

function getRandomColor(): string {
  return '#' + Math.floor(Math.random() * 16777215).toString(16);
}

const Entering = SlideInDown.springify().mass(1).stiffness(1000).damping(500);
const Exiting = ZoomOut.springify().mass(1).stiffness(1000).damping(500);

function ItemComponent({ item, index }: { item: Item; index: number }) {
  const rotation = useDerivedValue(() => withSpring(index * 15), [index]);

  const animatedStyle = useAnimatedStyle(() => {
    return {
      zIndex: -index,
      backgroundColor: item.color,
      transform: [
        {
          translateX: interpolate(rotation.value, [0, 100], [0, 100]),
        },
        {
          translateY: interpolate(rotation.value, [0, 100], [0, -30]),
        },
        {
          scale: interpolate(rotation.value, [0, 100], [1, 0.7]),
        },
        {
          rotate: `${rotation.value}deg`,
        },
      ],
    };
  }, [rotation, index, item]);

  return (
    <Animated.View
      entering={Entering}
      exiting={Exiting}
      style={[styles.item, animatedStyle]}
    />
  );
}

export default function StackedLayoutAnimationExample() {
  const [items, setItems] = useState<Item[]>([
    { color: 'red' },
    { color: 'green' },
  ]);

  useEffect(() => {
    const interval = setInterval(() => {
      setItems((i) => [{ color: getRandomColor() }, ...i]);
    }, 1500);
    return () => clearInterval(interval);
  }, []);

  return (
    <View style={styles.container}>
      {items.map((item, index) => {
        if (index > 2) {
          return null;
        }
        return <ItemComponent key={item.color} item={item} index={index} />;
      })}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  item: {
    width: 100,
    height: 150,
    borderRadius: 15,
    position: 'absolute',
  },
});
```
</details>

---------

Co-authored-by: Aleksandra Cynk <aleksandracynk@Aleksandras-MacBook-Pro-3.local>
Co-authored-by: Tomek Zawadzki <tomasz.zawadzki@swmansion.com>
@NiccoloCase
Copy link

I'm having the same issue when updating from v2 to v3. why this issue was closed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Layout Animations Platform: Android This issue is specific to Android Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snippet of code, snack or repo is provided
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants