Skip to content

Conversation

@bartlomiejbloniarz
Copy link
Contributor

@bartlomiejbloniarz bartlomiejbloniarz commented May 5, 2025

Summary

This PR brings back the Shared Element Transition feature to reanimated (within a limited scope and behind a feature flag).

Current limitations:

  • animations with native modals don't work on iOS (the modal is displayed over the Shared Element). This can be fixed by plugging the Shared Element into the Window, instead of the React's RootVIew.
  • can't define custom progress (swipe back) transitions (it just isn't implemented yet, it was easier to hard code)
  • progress transitions animate less props than basic ones (for the same reason as above)
  • on iOS there are still some issues with header height (e.g. the [SET] Modals) example
  • there are some performance bottlenecks, that stem from transforms being recalculated too eagerly (this requires some changes to how we recognize whether to start a transition)
  • we have to use a workaround for rnscreens to not keep a snapshot of the transitioning view on the popped screen. Currently the fix is only available in our patch, but should be soon merged into rnscreens.

Test plan

@slorber
Copy link
Contributor

slorber commented May 7, 2025

Hey 👋

Just backlinking to the PR announcement tweet for demos and community comments: https://x.com/BBloniarz_/status/1919390653856829566

@elozino-eryk
Copy link

Hi @bartlomiejbloniarz nice work you are doing.

Is there a way we can get a code demo of it in new Arch? Currently, I am unable to get it to work with the code sample provide in the docs.

Thanks ❤️

@bglgwyng
Copy link

@elozino-eryk you can see the set of examples by run yarn start in apps/fabric-example

@bartlomiejbloniarz bartlomiejbloniarz marked this pull request as ready for review November 4, 2025 15:29
@bartlomiejbloniarz bartlomiejbloniarz changed the title Shared Elements Transitions on the New Architecture Shared Element Transition on the New Architecture (feature flag) Nov 4, 2025
@piaskowyk piaskowyk self-requested a review November 5, 2025 19:27
Copy link
Member

@tomekzaw tomekzaw left a comment

Choose a reason for hiding this comment

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

  1. Please use const auto & instead of auto or auto & wherever possible.
  2. Please address leftover TODOs – are they "for now" or "for later"?
  3. Please remove leftover comments with code snippets if they are no longer needed
  4. Please add some spacing between switch cases for better readability.

function HomeScreen({ navigation }: HomeScreenProps) {
const [search, setSearch] = React.useState('');
const [wasClicked, setWasClicked] = React.useState<string[]>([]);
const platform = Platform.OS as 'ios' | 'android';
Copy link
Member

Choose a reason for hiding this comment

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

What about web and other platforms?

fontSize: 16,
color: 'black',
},
marker: {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
marker: {
shouldWorkEmoji: {

Comment on lines +92 to +95
type SharedTransitionProps = {
sharedTransitionTag?: string;
sharedTransitionStyle?: SharedTransition;
};
Copy link
Member

Choose a reason for hiding this comment

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

We have an unique chance to consider some breaking changes in the API, for instance:

  • renaming SharedTransition to SharedElementTransition
  • renaming sharedTransitionTag to sharedElementTransitionTag or sharedTag
  • renaming sharedTransitionStyle to something else without style

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think a more descriptive name would be cool, but it just is too long. Maybe just drop the style and have sharedTransiton like with layout animations? It would be confusing though with the tag being a separate thing

extends ComplexAnimationBuilder
implements ILayoutAnimationBuilder
{
static presetName = 'LinearTransition';
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
static presetName = 'LinearTransition';
static presetName = 'SharedTransition';

build = (): LayoutAnimationFunction => {
const delayFunction = this.getDelayFunction();
if (!this.durationV) {
this.durationV = 500;
Copy link
Member

Choose a reason for hiding this comment

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

Let's double check if the default config looks good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I think I inherit too much here from layout animations

ShadowView finalView, currentView, startView;
Tag parentTag;
std::optional<double> opacity;
int count = 1;
Copy link
Member

Choose a reason for hiding this comment

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

count of what?


const ShadowNode *LayoutAnimationsProxy_Experimental::findInShadowTreeByTag(const ShadowNode &node, Tag tag) const {
if (node.getTag() == tag) {
return const_cast<const ShadowNode *>(&node);
Copy link
Member

Choose a reason for hiding this comment

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

Can't it be done simpler?

if (node.getTag() == tag) {
return const_cast<const ShadowNode *>(&node);
}
for (auto &child : node.getChildren()) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
for (auto &child : node.getChildren()) {
for (const auto &child : node.getChildren()) {

}

auto oldView = oldChildShadowView;
Rect window{};
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Rect window{};
Rect window;

Comment on lines +1163 to +1164
auto &mutex = strongThis->mutex;
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
auto &mutex = strongThis->mutex;
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
auto lock = std::unique_lock<std::recursive_mutex>(strongThis->mutex);

"USE_SYNCHRONIZABLE_FOR_MUTABLES": true,
"USE_COMMIT_HOOK_ONLY_FOR_REACT_COMMITS": true
"USE_COMMIT_HOOK_ONLY_FOR_REACT_COMMITS": true,
"SHARED_ELEMENT_TRANSITIONS": true
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"SHARED_ELEMENT_TRANSITIONS": true
"ENABLE_SHARED_ELEMENT_TRANSITIONS": true

As was described in the las point of https://docs.swmansion.com/react-native-reanimated/docs/guides/feature-flags/#remarks-for-contributors

EXITING = 2,
LAYOUT = 3,
SHARED_ELEMENT_TRANSITION = 4,
SHARED_ELEMENT_TRANSITION_NATIVE_ID = 5,
Copy link
Member

Choose a reason for hiding this comment

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

It looks a bit sus that NATIVE_ID is a type of layout animation 🤔

build = (): LayoutAnimationFunction => {
const delayFunction = this.getDelayFunction();
if (!this.durationV) {
this.durationV = 500;
Copy link
Member

Choose a reason for hiding this comment

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

Maybe let's make it a bit faster, 300 would be better

Comment on lines +48 to +54
animations[key] = target.map(
(item: Record<string, number | string>) => {
key = Object.keys(item)[0];
return {
[key]: animationFactory(item[key]),
};
}
Copy link
Member

Choose a reason for hiding this comment

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

I know that this is a simple implementation from previous version, but we should keep in mind or note somewhere that this is not the correct way of doing matrix interpolation. Since we have higher priorities, let's keep it as it is for now.

if (!getStaticFeatureFlag('SHARED_ELEMENT_TRANSITIONS')) {
return;
}
if (this.props.sharedTransitionTag) {
Copy link
Member

Choose a reason for hiding this comment

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

This will probably prevent the removal of a shared transition from a component when someone assigns null or undefined.

Copy link
Member

Choose a reason for hiding this comment

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

I know that SET and LayoutAnimation are close features, but it think we need to split implementation of Layout animation proxy to maintain readability.

Comment on lines +294 to +295
auto transform = parseParentTransforms(node, absolutePositions);
transition.transform[index] = std::move(transform);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
auto transform = parseParentTransforms(node, absolutePositions);
transition.transform[index] = std::move(transform);
transition.transform[index] = parseParentTransforms(node, absolutePositions);

overrideTransform(transition.snapshot[1], transition.transform[1], propsParserContext);
transitions_.emplace_back(sharedTag, transition);
} else if (transition.parentTag[1]) {
// TODO: this is too eager
Copy link
Member

Choose a reason for hiding this comment

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

Could you explain this case?

}

void LayoutAnimationsProxy_Experimental::hideTransitioningViews(
int index,
Copy link
Member

Choose a reason for hiding this comment

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

We can replace this index with enum

lightNodes_[myTag] = std::move(node);

sharedTransitionManager_->containerTags_[sharedTag] = containerTag;
myTag += 2;
Copy link
Member

Choose a reason for hiding this comment

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

not the best name 😅

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants