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

scrollTo performance issues on the New Architecture when multiple views are animated at the same time #6999

Open
MatiPl01 opened this issue Feb 9, 2025 · 1 comment
Labels
Area: Performance Maintainer issue Issue created by a maintainer 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

@MatiPl01
Copy link
Member

MatiPl01 commented Feb 9, 2025

Description

Problem description

I was looking into possible improvements of auto scroll performance in my react-native-sortables library. Performance is fine on the Old Architecture, but on the New Architecture it is horrible. Apart from massive FPS drops and noticeably higher memory usage (might be related to RN, memory is not a big problem), there are also a some other problems:

  • scrollTo runs synchronously, whilst style updates are asynchronous, which results in the noticeable delay between ScrollView position update and the view position update,
  • this delay is higher when the number of animated views is increased (probably because of the style updates batching system and conflicts between Reanimated and RN commits)

Example recording

Old Architecture (Paper) New Architecture (Fabric)
Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-02-09.at.15.02.03.mp4
Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-02-09.at.15.00.26.mp4

What I tried

To fix the problem with delay between view update and the ScrollView position update I tried to read the view position with the synchronous measure function from Reanimated and scroll the ScrollView only after the view was transitioned. This approach also doesn't work as, likely, the new view position is applied somewhere between frames in the useFrameCallback and there is still some delay between transformation of the view and scrolling the ScrollView.

Final remarks

The issue is less visible in the release build on the real device, likely because of more FPS, which result in more frequent commits and lower delays.

Steps to reproduce

Copy this code to your app and run it on the Old and the New Architecture:

import { useMemo } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Animated, {
  measure,
  scrollTo,
  useAnimatedReaction,
  useAnimatedRef,
  useAnimatedStyle,
  useFrameCallback,
  useSharedValue,
  withRepeat,
  withSequence,
  withTiming
} from 'react-native-reanimated';

const ZERO_VECTOR = { x: 0, y: 0 };

export default function PlaygroundExample() {
  const animatedRef = useAnimatedRef<Animated.ScrollView>();
  const itemRef = useAnimatedRef<Animated.View>();
  const containerRef = useAnimatedRef<Animated.View>();

  const startScrollOffset = useSharedValue<number | null>(null);
  const startPosition = useSharedValue(ZERO_VECTOR);
  const currentPosition = useSharedValue(ZERO_VECTOR);
  const newScrollOffset = useSharedValue(0);

  const views = useMemo(() => {
    return Array.from({ length: 100 }, (_, index) => {
      const blueShade = `hsl(210, 80%, ${100 - index}%)`;
      return <Item color={blueShade} name={`Item ${index + 1}`} key={index} />;
    });
  }, []);

  useAnimatedReaction(
    () => ({
      offsetDiff: newScrollOffset.value - (startScrollOffset.value ?? 0)
    }),
    ({ offsetDiff }) => {
      currentPosition.value = {
        x: startPosition.value.x,
        y: startPosition.value.y + offsetDiff
      };
    }
  );

  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [
        { translateX: currentPosition.value.x },
        { translateY: currentPosition.value.y }
      ]
    };
  });

  useFrameCallback(() => {
    const scrollableMeasurements = measure(animatedRef);
    const itemMeasurements = measure(itemRef);
    const containerMeasurements = measure(containerRef);
    if (
      !scrollableMeasurements ||
      !itemMeasurements ||
      !containerMeasurements
    ) {
      return;
    }

    const itemY = itemMeasurements.pageY - containerMeasurements.pageY;

    const offsetDiff = itemY - (startPosition.value.y ?? 0);

    newScrollOffset.value = newScrollOffset.value + 5;
    scrollTo(animatedRef, 0, (offsetDiff + newScrollOffset.value) / 2, false);
  });

  return (
    <Animated.ScrollView ref={animatedRef} contentContainerStyle={{ gap: 20 }}>
      <View ref={containerRef}>
        {views}
        <Animated.View
          ref={itemRef}
          style={[styles.absoluteItem, animatedStyle]}
        />
      </View>
    </Animated.ScrollView>
  );
}

function Item({ color, name }: { name: string; color: string }) {
  const heavyStyle = useAnimatedStyle(() => {
    return {
      left: withRepeat(withSequence(withTiming(100), withTiming(0)), -1, true)
    };
  });

  return (
    <Animated.View
      style={[styles.item, heavyStyle, { backgroundColor: color }]}>
      <Text style={styles.text}>{name}</Text>
    </Animated.View>
  );
}

const styles = StyleSheet.create({
  item: {
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'red',
    height: 100
  },
  absoluteItem: {
    position: 'absolute',
    backgroundColor: 'red',
    height: 100,
    width: 100
  },
  text: {
    fontWeight: 'bold'
  }
});

Snack or a link to a repository

https://github.com/MatiPl01/react-native-sortables <- you can test it here as well on the Auto Scroll example screen

Reanimated version

3.16.6

React Native version

0.76.5

Platforms

iOS, Android

JavaScript runtime

None

Workflow

None

Architecture

Fabric (New Architecture)

Build type

None

Device

None

Device model

No response

Acknowledgements

Yes

Copy link

github-actions bot commented Feb 9, 2025

Hey! 👋

It looks like you've omitted a few important sections from the issue template.

Please complete Description section.

@github-actions github-actions bot added Missing info The user didn't precise the problem enough 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 labels Feb 9, 2025
@MatiPl01 MatiPl01 removed the Missing info The user didn't precise the problem enough label Feb 9, 2025
@MatiPl01 MatiPl01 changed the title scrollTo performance on the New Architecture is horrible when multiple views are animated at the same time scrollTo performance issues on the New Architecture when multiple views are animated at the same time Feb 10, 2025
@github-actions github-actions bot added the Missing info The user didn't precise the problem enough label Feb 10, 2025
@tjzel tjzel added Maintainer issue Issue created by a maintainer and removed Missing info The user didn't precise the problem enough labels Feb 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Performance Maintainer issue Issue created by a maintainer 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

No branches or pull requests

2 participants