Skip to content

Commit f154a93

Browse files
committed
fix: fixed no or stale transition when switching route (goBack) too fast
1 parent cd652dc commit f154a93

File tree

5 files changed

+175
-82
lines changed

5 files changed

+175
-82
lines changed

src/SharedElementRendererData.tsx

Lines changed: 129 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,21 @@ import {
44
SharedElementsStrictConfig,
55
SharedElementAnimatedValue,
66
SharedElementTransitionProps,
7+
Route,
78
} from './types';
89
import { normalizeSharedElementsConfig } from './utils';
910

1011
export type SharedElementRendererUpdateHandler = () => any;
1112

1213
export interface ISharedElementRendererData {
13-
startTransition(animValue: SharedElementAnimatedValue): void;
14-
endTransition(): void;
15-
willActivateScene(sceneData: SharedElementSceneData): void;
16-
didActivateScene(sceneData: SharedElementSceneData): void;
14+
startTransition(
15+
animValue: SharedElementAnimatedValue,
16+
route: Route,
17+
prevRoute: Route
18+
): void;
19+
endTransition(route: Route, prevRoute: Route): void;
20+
willActivateScene(sceneData: SharedElementSceneData, route: Route): void;
21+
didActivateScene(sceneData: SharedElementSceneData, route: Route): void;
1722
}
1823

1924
function getSharedElements(
@@ -28,67 +33,138 @@ function getSharedElements(
2833
);
2934
}
3035

36+
const NO_SHARED_ELEMENTS: any[] = [];
37+
38+
type SceneRoute = {
39+
scene: SharedElementSceneData;
40+
route: Route;
41+
subscription: SharedElementEventSubscription | null;
42+
};
43+
3144
export default class SharedElementRendererData
3245
implements ISharedElementRendererData {
33-
private sceneData: SharedElementSceneData | null = null;
34-
private prevSceneData: SharedElementSceneData | null = null;
46+
private scenes: SceneRoute[] = [];
3547
private updateSubscribers = new Set<SharedElementRendererUpdateHandler>();
36-
private sceneSubscription: SharedElementEventSubscription | null = null;
37-
private sharedElements: SharedElementsStrictConfig = [];
48+
private sharedElements: SharedElementsStrictConfig | null = null;
3849
private isShowing: boolean = true;
3950
private animValue: SharedElementAnimatedValue;
51+
private route: Route | null = null;
52+
private prevRoute: Route | null = null;
53+
private scene: SharedElementSceneData | null = null;
54+
private prevScene: SharedElementSceneData | null = null;
4055

41-
startTransition(animValue: SharedElementAnimatedValue) {
56+
startTransition(
57+
animValue: SharedElementAnimatedValue,
58+
route: Route,
59+
// @ts-ignore
60+
prevRoute: Route //eslint-disable-line @typescript-eslint/no-unused-vars
61+
) {
62+
//console.log('startTransition, route: ', route.key);
4263
this.animValue = animValue;
64+
this.prevRoute = this.route;
65+
this.route = route;
66+
this.updateSceneListeners();
67+
this.updateSharedElements();
68+
}
69+
70+
endTransition(
71+
// @ts-ignore
72+
route: Route, //eslint-disable-line @typescript-eslint/no-unused-vars
73+
// @ts-ignore
74+
prevRoute: Route //eslint-disable-line @typescript-eslint/no-unused-vars
75+
) {
76+
//console.log('endTransition, route: ', route.key);
77+
if (this.prevRoute != null) {
78+
this.prevRoute = null;
79+
this.animValue = null;
80+
this.updateSceneListeners();
81+
this.updateSharedElements();
82+
}
83+
}
84+
85+
willActivateScene(sceneData: SharedElementSceneData, route: Route): void {
86+
//console.log('willActivateScene, route: ', route.key);
87+
this.registerScene(sceneData, route);
88+
}
89+
90+
didActivateScene(sceneData: SharedElementSceneData, route: Route): void {
91+
//console.log('didActivateScene, route: ', route.key);
92+
this.prevRoute = null;
93+
this.registerScene(sceneData, route);
4394
}
4495

45-
endTransition() {
46-
// Nothing to do
96+
private registerScene(sceneData: SharedElementSceneData, route: Route) {
97+
this.scenes.push({
98+
scene: sceneData,
99+
route,
100+
subscription: null,
101+
});
102+
if (this.scenes.length > 5) {
103+
const { subscription } = this.scenes[0];
104+
this.scenes.splice(0, 1);
105+
if (subscription) subscription.remove();
106+
}
107+
this.updateSceneListeners();
108+
this.updateSharedElements();
47109
}
48110

49-
willActivateScene(sceneData: SharedElementSceneData): void {
50-
/*console.log(
51-
'SharedElementRendererData.willActivateScene: ',
52-
sceneData.name,
53-
', previous: ',
54-
this.prevSceneData ? this.prevSceneData.name : ''
55-
);*/
56-
if (!this.prevSceneData) return;
111+
private updateSceneListeners() {
112+
this.scenes.forEach(sceneRoute => {
113+
const { scene, route, subscription } = sceneRoute;
114+
const isActive =
115+
(this.route && this.route.key === route.key) ||
116+
(this.prevRoute && this.prevRoute.key === route.key);
117+
if (isActive && !subscription) {
118+
sceneRoute.subscription = scene.addUpdateListener(() => {
119+
// TODO optimize
120+
this.emitUpdateEvent();
121+
});
122+
} else if (!isActive && subscription) {
123+
sceneRoute.subscription = null;
124+
subscription.remove();
125+
}
126+
});
127+
}
128+
129+
private updateSharedElements() {
130+
const { route, prevRoute, animValue } = this;
131+
const sceneRoute = route
132+
? this.scenes.find(sc => sc.route.key === route.key)
133+
: undefined;
134+
const prevSceneRoute = prevRoute
135+
? this.scenes.find(sc => sc.route.key === prevRoute.key)
136+
: undefined;
137+
const scene = sceneRoute ? sceneRoute.scene : null;
138+
const prevScene = prevSceneRoute ? prevSceneRoute.scene : null;
139+
140+
// Update current scene & previous scene
141+
if (scene === this.scene && prevScene === this.prevScene) return;
142+
this.scene = scene;
143+
this.prevScene = prevScene;
144+
145+
// Update shared elements
146+
let sharedElements: SharedElementsStrictConfig | null = null;
57147
let isShowing = true;
58-
let sharedElements = getSharedElements(sceneData, this.prevSceneData, true);
59-
if (!sharedElements) {
60-
isShowing = false;
61-
sharedElements = getSharedElements(this.prevSceneData, sceneData, false);
148+
if (animValue && scene && prevScene) {
149+
sharedElements = getSharedElements(scene, prevScene, true);
150+
if (!sharedElements) {
151+
isShowing = false;
152+
sharedElements = getSharedElements(prevScene, scene, false);
153+
}
62154
}
63-
if (sharedElements && sharedElements.length) {
64-
// console.log('sharedElements: ', sharedElements, sceneData);
65-
this.sceneData = sceneData;
155+
if (this.sharedElements !== sharedElements) {
66156
this.sharedElements = sharedElements;
67157
this.isShowing = isShowing;
68-
this.sceneSubscription = this.sceneData.addUpdateListener(() => {
69-
// TODO optimize
70-
this.emitUpdateEvent();
71-
});
158+
/*console.log(
159+
'updateSharedElements: ',
160+
sharedElements,
161+
' ,isShowing: ',
162+
isShowing
163+
);*/
72164
this.emitUpdateEvent();
73165
}
74166
}
75167

76-
didActivateScene(sceneData: SharedElementSceneData): void {
77-
//console.log('SharedElementRendererData.didActivateScene: ', sceneData.name);
78-
if (this.sceneSubscription) {
79-
this.sceneSubscription.remove();
80-
this.sceneSubscription = null;
81-
}
82-
this.prevSceneData = sceneData;
83-
if (this.sceneData) {
84-
this.sceneData = null;
85-
if (this.sharedElements.length) {
86-
this.sharedElements = [];
87-
this.emitUpdateEvent();
88-
}
89-
}
90-
}
91-
92168
addUpdateListener(
93169
handler: SharedElementRendererUpdateHandler
94170
): SharedElementEventSubscription {
@@ -103,23 +179,21 @@ export default class SharedElementRendererData
103179
}
104180

105181
getTransitions(): SharedElementTransitionProps[] {
106-
const { sharedElements, prevSceneData, sceneData, isShowing } = this;
182+
const { sharedElements, prevScene, scene, isShowing, animValue } = this;
107183
// console.log('getTransitions: ', sharedElements);
184+
if (!sharedElements || !scene || !prevScene) return NO_SHARED_ELEMENTS;
108185
return sharedElements.map(({ id, otherId, ...other }) => {
109186
const startId = isShowing ? otherId || id : id;
110187
const endId = isShowing ? id : otherId || id;
111188
return {
112-
position: this.animValue,
189+
position: animValue,
113190
start: {
114-
ancestor:
115-
(prevSceneData ? prevSceneData.getAncestor() : undefined) || null,
116-
node:
117-
(prevSceneData ? prevSceneData.getNode(startId) : undefined) ||
118-
null,
191+
ancestor: (prevScene ? prevScene.getAncestor() : undefined) || null,
192+
node: (prevScene ? prevScene.getNode(startId) : undefined) || null,
119193
},
120194
end: {
121-
ancestor: (sceneData ? sceneData.getAncestor() : undefined) || null,
122-
node: (sceneData ? sceneData.getNode(endId) : undefined) || null,
195+
ancestor: (scene ? scene.getAncestor() : undefined) || null,
196+
node: (scene ? scene.getNode(endId) : undefined) || null,
123197
},
124198
...other,
125199
};

src/SharedElementRendererProxy.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,54 @@
11
import SharedElementRendererData, {
22
ISharedElementRendererData,
33
} from './SharedElementRendererData';
4-
import { SharedElementAnimatedValue } from './types';
4+
import { SharedElementAnimatedValue, Route } from './types';
55
import SharedElementSceneData from './SharedElementSceneData';
66

77
export class SharedElementRendererProxy implements ISharedElementRendererData {
88
private data: SharedElementRendererData | null = null;
99

10-
startTransition(animValue: SharedElementAnimatedValue) {
10+
startTransition(
11+
animValue: SharedElementAnimatedValue,
12+
route: Route,
13+
prevRoute: Route
14+
) {
1115
if (!this.data) {
1216
console.warn(
1317
'SharedElementRendererProxy.startTransition called before Proxy was initialized'
1418
);
1519
return;
1620
}
17-
return this.data.startTransition(animValue);
21+
return this.data.startTransition(animValue, route, prevRoute);
1822
}
1923

20-
endTransition() {
24+
endTransition(route: Route, prevRoute: Route) {
2125
if (!this.data) {
2226
console.warn(
2327
'SharedElementRendererProxy.endTransition called before Proxy was initialized'
2428
);
2529
return;
2630
}
27-
return this.data.endTransition();
31+
return this.data.endTransition(route, prevRoute);
2832
}
2933

30-
willActivateScene(sceneData: SharedElementSceneData) {
34+
willActivateScene(sceneData: SharedElementSceneData, route: Route) {
3135
if (!this.data) {
3236
console.warn(
3337
'SharedElementRendererProxy.willActivateScene called before Proxy was initialized'
3438
);
3539
return;
3640
}
37-
return this.data.willActivateScene(sceneData);
41+
return this.data.willActivateScene(sceneData, route);
3842
}
3943

40-
didActivateScene(sceneData: SharedElementSceneData) {
44+
didActivateScene(sceneData: SharedElementSceneData, route: Route) {
4145
if (!this.data) {
4246
console.warn(
4347
'SharedElementRendererProxy.didActivateScene called before Proxy was initialized'
4448
);
4549
return;
4650
}
47-
return this.data.didActivateScene(sceneData);
51+
return this.data.didActivateScene(sceneData, route);
4852
}
4953

5054
get source(): SharedElementRendererData | null {

src/createSharedElementScene.tsx

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
SharedElementSceneComponent,
1111
} from './types';
1212
import { ISharedElementRendererData } from './SharedElementRendererData';
13+
import { getActiveRouteState } from './utils';
1314

1415
const styles = StyleSheet.create({
1516
container: {
@@ -21,18 +22,6 @@ type PropsType = {
2122
navigation: NavigationProp;
2223
};
2324

24-
function getActiveRouteState(route: any): any {
25-
if (
26-
!route.routes ||
27-
route.routes.length === 0 ||
28-
route.index >= route.routes.length
29-
) {
30-
return route;
31-
} else {
32-
return getActiveRouteState(route.routes[route.index]);
33-
}
34-
}
35-
3625
function createSharedElementScene(
3726
Component: SharedElementSceneComponent,
3827
rendererData: ISharedElementRendererData
@@ -84,15 +73,20 @@ function createSharedElementScene(
8473
};
8574

8675
private onWillFocus = () => {
87-
rendererData.willActivateScene(this.sceneData);
76+
const { navigation } = this.props;
77+
const activeRoute = getActiveRouteState(navigation.state);
78+
if (navigation.state.routeName === activeRoute.routeName) {
79+
// console.log('onWillFocus: ', navigation.state, activeRoute);
80+
rendererData.willActivateScene(this.sceneData, navigation.state);
81+
}
8882
};
8983

9084
private onDidFocus = () => {
9185
const { navigation } = this.props;
9286
const activeRoute = getActiveRouteState(navigation.state);
9387
if (navigation.state.routeName === activeRoute.routeName) {
9488
// console.log('onDidFocus: ', this.sceneData.name, navigation);
95-
rendererData.didActivateScene(this.sceneData);
89+
rendererData.didActivateScene(this.sceneData, navigation.state);
9690
}
9791
};
9892
}

src/createSharedElementStackNavigator.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import SharedElementRendererData, {
77
import createSharedElementScene from './createSharedElementScene';
88
import SharedElementRendererContext from './SharedElementRendererContext';
99
import { SharedElementRendererProxy } from './SharedElementRendererProxy';
10+
import { getActiveRouteState } from './utils';
1011

1112
function createSharedElementEnabledNavigator(
1213
createNavigator: any,
@@ -44,15 +45,22 @@ function createSharedElementEnabledNavigator(
4445
position.interpolate({
4546
inputRange: [index - 1, index],
4647
outputRange: index > prevIndex ? [0, 1] : [2, 1],
47-
})
48+
}),
49+
getActiveRouteState(transitionProps.scene.route),
50+
getActiveRouteState(prevTransitionProps.scene.route)
4851
);
4952
if (navigatorConfig && navigatorConfig.onTransitionStart) {
50-
navigatorConfig.onTransitionStart(transitionProps, prevTransitionProps);
53+
navigatorConfig.onTransitionStart(
54+
getActiveRouteState(transitionProps.scene.route),
55+
getActiveRouteState(prevTransitionProps.scene.route)
56+
);
5157
}
5258
},
5359
onTransitionEnd: (transitionProps: any, prevTransitionProps: any) => {
54-
// console.log('onTransitionEnd: ', transitionProps, prevTransitionProps);
55-
rendererData.endTransition();
60+
rendererData.endTransition(
61+
transitionProps.scene.route,
62+
prevTransitionProps.scene.route
63+
);
5664
if (navigatorConfig && navigatorConfig.onTransitionEnd) {
5765
navigatorConfig.onTransitionEnd(transitionProps, prevTransitionProps);
5866
}

0 commit comments

Comments
 (0)