Instantly share code, notes, and snippets.
Last active
October 31, 2018 21:13
-
Star
2
(2)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save birkir/bbcd1ce584a568ec108db15b291a8710 to your computer and use it in GitHub Desktop.
SharedElementTransition for ios using react-native-navigation by wix
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
do you have any example how to use?