Skip to content

fix: zIndex for exiting animations#9541

Open
kasperski95 wants to merge 21 commits into
software-mansion:mainfrom
kasperski95:kas/fix-exiting-zindex--2
Open

fix: zIndex for exiting animations#9541
kasperski95 wants to merge 21 commits into
software-mansion:mainfrom
kasperski95:kas/fix-exiting-zindex--2

Conversation

@kasperski95

@kasperski95 kasperski95 commented May 27, 2026

Copy link
Copy Markdown

Summary

This MR changes the exiting animations' underlying logic. Currently, Reanimated re-inserts the exiting view component at the end of the children array, which breaks the z-order. The new approach doesn't send remove and delete mutations immediately and translates mutation indices instead.

Test plan

  1. Run BBExample (check attachments)
  2. Run LA examples to check for regressions

Attachments

Before iOS

BB.before.ios.mov

After iOS

BB.after.ios.mov

After Android

BB.after.android.mov

Unrelated Android Issue

(It exists on the main branch)
android issue

@kasperski95 kasperski95 marked this pull request as ready for review May 28, 2026 11:01
react_native_assert(index >= 0 && "index must be non-negative");
int remainingNonExitingChildrenToCheck = index;
int exitingCount = 0;
for (std::size_t i = 0; i < children.size(); i++) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice: this may affect the performance. RN flattens views so the children size could be large.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could store the number of animated children on the parent, this way we could short-circuit this method to return immediately if there are none.

We should also expect that the children indices in mutations are increasing within one transaction, this way we could reuse the calculation up to a certain point (if the assumption is wrong at any point we should just recalculate from scratch - this could happen if multiple transactions are combined into one on Android)

childTags += "(exiting)";
}
}
LOG(WARNING) << "Remove mutation index mismatch: expected tag " << mutation.oldChildShadowView.tag

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left the log on purpose. It helps with debugging if the assertion below fails. Logs are displayed only on iOS. On Android, RN uses some custom solution to log glog's sink.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think SYSLOG works on both iOS and Android. Maybe we could also extract this error logic to a helper as it takes up much space here

Comment on lines +186 to +188
if (it == lightNodes_.end()) {
break;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the assert, we don't really need this check. It could be useful for Release mode, but I think those defensive checks in here are not helping us in the long run

auto lock = std::unique_lock<std::recursive_mutex>(mutex);
const PropsParserContext propsParserContext{surfaceId, *contextContainer_};
ShadowViewMutationList filteredMutations;
ShadowViewMutationList outputMutations;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the renames here are mostly a good idea, though they make the review less convenient as many lines are affected for unrelated reasons.

childTags += "(exiting)";
}
}
LOG(WARNING) << "Remove mutation index mismatch: expected tag " << mutation.oldChildShadowView.tag

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think SYSLOG works on both iOS and Android. Maybe we could also extract this error logic to a helper as it takes up much space here

react_native_assert(index >= 0 && "index must be non-negative");
int remainingNonExitingChildrenToCheck = index;
int exitingCount = 0;
for (std::size_t i = 0; i < children.size(); i++) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could store the number of animated children on the parent, this way we could short-circuit this method to return immediately if there are none.

We should also expect that the children indices in mutations are increasing within one transaction, this way we could reuse the calculation up to a certain point (if the assumption is wrong at any point we should just recalculate from scratch - this could happen if multiple transactions are combined into one on Android)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants