Skip to content

Instantly share code, notes, and snippets.

@birkir
Last active October 31, 2018 21:13
Show Gist options
  • Save birkir/bbcd1ce584a568ec108db15b291a8710 to your computer and use it in GitHub Desktop.
Save birkir/bbcd1ce584a568ec108db15b291a8710 to your computer and use it in GitHub Desktop.
SharedElementTransition for ios using react-native-navigation by wix
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { Dimensions, Animated, View, Text, findNodeHandle } from 'react-native';
import { Navigation } from 'react-native-navigation';
import Modal from './Modal';
const RCTUIManager = require('NativeModules').UIManager;
const data = {
translateX: new Animated.Value(0),
translateY: new Animated.Value(0),
scaleX: new Animated.Value(1),
scaleY: new Animated.Value(1),
cursor: new Animated.Value(0),
};
const duration = 3300;
const getLayout = (id, node) => new Promise((resolve, reject) => {
try {
const nodeHandle = findNodeHandle(node);
setTimeout(() => RCTUIManager.measureInWindow(nodeHandle, (x, y, width, height, pageX, pageY) => {
console.log('measure', id, x, y, width, height, pageX, pageY);
resolve({ x, y, width, height });
}));
} catch (err) {
console.log('Error measuring node %o', err);
resolve(null);
}
});
export default class SharedElementTransition extends PureComponent {
static propTypes = {
children: PropTypes.node,
sharedElementId: PropTypes.string,
showDuration: PropTypes.number,
hideDuration: PropTypes.number,
};
static defaultProps = {
showDuration: 600,
hideDuration: 400,
};
static canGoBack = false;
static async fromEvent(screen, e) {
if (e.id === 'willDisappear') {
data.fromScreen = screen;
const { width, height } = await getLayout('from', data.fromComponent);
// Set width and height
data.width = width;
data.height = height;
// Setup lightbox component screen
Navigation.registerComponent('MODAL_NAV_FROM', () => () =>
React.createElement(SharedElementTransition, { isMock: true }, React.cloneElement(data.fromComponent.props.children)));
// Display modal
data.fromScreen.props.navigator.showLightBox({
screen: 'MODAL_NAV_FROM',
style: {
animated: false,
},
passProps: {},
});
data.fromComponent.hide(data.fromLayout);
}
if (e.id === 'willAppear') {
if (SharedElementTransition.canGoBack) {
data.fromComponent.hide(data.fromLayout);
data.toComponent.hide(data.toLayout);
data.fromScreen.props.navigator.showLightBox({
screen: 'MODAL_NAV_FROM',
style: {
animated: false,
},
passProps: {},
});
Animated.parallel([
Animated.timing(data.translateX, { toValue: data.fromLayout.x, duration }),
Animated.timing(data.translateY, { toValue: data.fromLayout.y, duration }),
Animated.timing(data.scaleX, { toValue: 1, duration }),
Animated.timing(data.scaleY, { toValue: 1, duration }),
Animated.timing(data.cursor, { toValue: 1, duration }),
])
.start(() => {
data.fromComponent.show();
data.toComponent.show();
screen.props.navigator.dismissLightBox();
SharedElementTransition.canGoBack = false;
});
}
}
}
static async toEvent(screen, e) {
if (e.id === 'willAppear') {
data.toScreen = screen;
}
if (e.id === 'didAppear') {
screen.props.navigator.dismissLightBox();
}
}
static onBothReady() {
if (SharedElementTransition.canGoBack) {
return false;
}
if (data.fromLayout && !data.toLayout) {
data.translateX.setValue(data.fromLayout.x);
data.translateY.setValue(data.fromLayout.y);
}
if (!data.fromLayout || !data.toLayout) return;
data.toComponent.hide(data.toLayout);
const scaleX = data.toLayout.width / data.fromLayout.width;
const scaleY = data.toLayout.height / data.fromLayout.height;
const deltaX = (data.toLayout.width - data.fromLayout.width) / 2;
const deltaY = (data.toLayout.height - data.fromLayout.height) / 2;
const x = data.toLayout.x + deltaX;
const y = data.toLayout.y + deltaY;
Animated.parallel([
Animated.timing(data.translateX, { toValue: x, duration }),
Animated.timing(data.translateY, { toValue: y, duration }),
Animated.timing(data.scaleX, { toValue: scaleX, duration }),
Animated.timing(data.scaleY, { toValue: scaleY, duration }),
])
.start(() => {
data.fromComponent.show();
data.toComponent.show();
SharedElementTransition.canGoBack = true;
});
}
constructor(...args) {
super(...args);
if (!data.fromComponent) {
data.fromComponent = this;
this.isFrom = true;
} else if (this.props.isMock) {
data.mockComponent = this;
this.isMock = true;
} else {
data.toComponent = this;
this.isTo = true;
}
}
componentDidMount() {
if (this.isFrom) {
getLayout('didmount from', this)
.then(layout => {
data.fromLayout = layout;
SharedElementTransition.onBothReady();
});
}
if (this.isTo) {
getLayout('didmount to', this)
.then(layout => {
data.toLayout = layout;
SharedElementTransition.onBothReady();
});
}
}
state = {
hidden: false,
width: 0,
height: 0,
};
hide({ width, height }) {
this.setState({ hidden: true, width, height });
}
show() {
this.setState({ hidden: false });
}
render() {
const { isMock, children } = this.props;
if (isMock) {
const { width, height, translateX, translateY, scaleX, scaleY } = data;
const transform = [
{ translateX }, { translateY },
{ scaleX }, { scaleY }
];
return (
<Animated.View style={{ width, height, transform }}>
{React.cloneElement(children, { cursor: data.cursor })}
</Animated.View>
);
}
if (this.state.hidden) {
const { width, height } = this.state;
return <View style={{ width, height }} />;
}
return React.cloneElement(children, { cursor: data.cursor });
}
}
@alejoasotelo
Copy link

do you have any example how to use?

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