diff options
| author | anderklander <anderklander@gmail.com> | 2018-02-21 14:46:07 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-02-26 16:15:52 +0100 |
| commit | 608029ca1c403101a88ec45d52e44dc1f22e434f (patch) | |
| tree | f2f7bb91c19f6d01d259e785242dbd967bc6392f /app | |
| parent | 47a78129cd2f953fe4dca33a04c138ac2093a52e (diff) | |
| download | mullvadvpn-608029ca1c403101a88ec45d52e44dc1f22e434f.tar.xz mullvadvpn-608029ca1c403101a88ec45d52e44dc1f22e434f.zip | |
Add TransitionContainer using ReactXP animations
Diffstat (limited to 'app')
| -rw-r--r-- | app/assets/css/style.css | 1 | ||||
| -rw-r--r-- | app/assets/css/transitions.css | 91 | ||||
| -rw-r--r-- | app/components/TransitionContainer.js | 201 | ||||
| -rw-r--r-- | app/routes.js | 6 | ||||
| -rw-r--r-- | app/transitions.js | 48 |
5 files changed, 222 insertions, 125 deletions
diff --git a/app/assets/css/style.css b/app/assets/css/style.css index 5021d7d1a9..7d667f67e3 100644 --- a/app/assets/css/style.css +++ b/app/assets/css/style.css @@ -3,7 +3,6 @@ @import 'fonts.css'; @import 'global.css'; @import 'buttons.css'; -@import 'transitions.css'; /* app */ @import '../../components/PlatformWindow.css'; diff --git a/app/assets/css/transitions.css b/app/assets/css/transitions.css deleted file mode 100644 index 239be5b63b..0000000000 --- a/app/assets/css/transitions.css +++ /dev/null @@ -1,91 +0,0 @@ -/** - * CSS rules for transitions using React-router and CSSTransitionGroup - */ - -div[class*="-transition-leave"], div[class*="-transition-enter"] { - /* keep animated .layout divs pinned to viewport boundaries */ - position: absolute; - top: 0; - left: 0; - width: 100vw; - - /* disable UI interaction during transitions */ - pointer-events: none; -} - -.transition-container { - position: relative; - width: 100vw; - height: 100vh; - overflow: hidden; -} - -/* New view slides bottom top */ - -.slide-up-transition-leave { z-index: 0; } -.slide-up-transition-enter { - transform: translateY(100vh); - z-index: 1; -} - -.slide-up-transition-enter.slide-up-transition-enter-active { - transform: translateY(0); - transition: transform 450ms ease; -} - -/* New view slides top bottom */ - -.slide-down-transition-enter { z-index: 0; } -.slide-down-transition-leave { - transform: translateY(0); - z-index: 1; -} - -.slide-down-transition-leave.slide-down-transition-leave-active { - transform: translateY(100vh); - transition: transform 450ms ease; -} - -/* New view slids right to left */ - -.push-transition-leave { - transform: translateX(0vw); - z-index: 0; -} - -.push-transition-enter { - transform: translateX(100vw); - z-index: 1; -} - -.push-transition-leave.push-transition-leave-active { - transform: translateX(-50vw); - transition: transform 450ms ease; -} - -.push-transition-enter.push-transition-enter-active { - transform: translateX(0); - transition: transform 450ms ease; -} - -/* New view slides left to right */ - -.pop-transition-enter { - transform: translateX(-50vw); - z-index: 0; -} - -.pop-transition-leave { - transform: translateX(0); - z-index: 1; -} - -.pop-transition-enter.pop-transition-enter-active { - transform: translateX(0vw); - transition: transform 450ms ease; -} - -.pop-transition-leave.pop-transition-leave-active { - transform: translateX(100vw); - transition: transform 450ms ease; -}
\ No newline at end of file diff --git a/app/components/TransitionContainer.js b/app/components/TransitionContainer.js new file mode 100644 index 0000000000..c4a011a978 --- /dev/null +++ b/app/components/TransitionContainer.js @@ -0,0 +1,201 @@ +// @flow +import * as React from 'react'; +import { Styles, Component, Animated, View, Types, UserInterface } from 'reactxp'; +import type { TransitionGroupProps } from '../transitions'; + +type TransitionContainerProps = { + children: React.Node, + ...TransitionGroupProps +}; + +type State = { + previousChildren: ?React.Node, + childrenAnimation: Types.AnimatedViewStyleRuleSet, + previousChildrenAnimation: Types.AnimatedViewStyleRuleSet, + animationStyles: Types.AnimatedViewStyleRuleSet, + dimensions: Types.Dimensions, +}; + +export default class TransitionContainer extends Component<TransitionContainerProps, State> { + + constructor(props: TransitionContainerProps) { + super(props); + + const dimensions = UserInterface.measureWindow(); + + this.state = { + dimensions, + animationStyles: { + style: Styles.createAnimatedViewStyle({ + position: 'absolute', + width: dimensions.width, + height: dimensions.height, + }), + allowPointerEvents: Styles.createAnimatedViewStyle({ + pointerEvents: 'auto', + }) + } + }; + } + + componentWillReceiveProps(nextProps: TransitionContainerProps) { + switch (nextProps.name){ + case 'slide-up': + this.slideUpTransition(nextProps); + break; + case 'slide-down': + this.slideDownTransition(nextProps); + break; + case 'push': + this.pushTransition(nextProps); + break; + case 'pop': + this.popTransition(nextProps); + break; + default: + break; + } + } + + onFinishedAnimation() { + this.setState({ + childrenAnimation: this.state.animationStyles.allowPointerEvents, + previousChildren: null + }); + } + + slideUpTransition(nextProps: TransitionContainerProps) { + const currentTranslationValue = Animated.createValue(this.state.dimensions.height); + this.setState({ + previousChildren: this.props.children, + childrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 1, + transform: [{ translateY: currentTranslationValue }] + }), + previousChildrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 0, + transform: [{ translateY: 0 }] + }), + }, () => { + Animated.timing(currentTranslationValue, { + toValue: 0, + easing: Animated.Easing.InOut(), + duration: nextProps.duration, + useNativeDriver: true, + }).start(() => this.onFinishedAnimation()); + }); + } + + slideDownTransition(nextProps: TransitionContainerProps) { + const previousTranslationValue = Animated.createValue(0); + this.setState({ + previousChildren: this.props.children, + childrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 0, + transform: [{ translateY: 0 }] + }), + previousChildrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 1, + transform: [{ translateY: previousTranslationValue }] + }), + }, () => { + Animated.timing(previousTranslationValue, { + toValue: this.state.dimensions.height, + easing: Animated.Easing.InOut(), + duration: nextProps.duration, + useNativeDriver: true, + }).start(() => this.onFinishedAnimation()); + }); + } + + pushTransition(nextProps: TransitionContainerProps) { + const currentTranslationValue = Animated.createValue(this.state.dimensions.width); + const previousTranslationValue = Animated.createValue(0); + this.setState({ + previousChildren: this.props.children, + childrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 1, + transform: [{ translateX: currentTranslationValue }] + }), + previousChildrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 0, + transform: [{ translateX: previousTranslationValue }] + }), + }, () => { + const compositeAnimation = Animated.parallel([ + Animated.timing(currentTranslationValue, { + toValue: 0, + easing: Animated.Easing.InOut(), + duration: nextProps.duration, + useNativeDriver: true, + }), + Animated.timing(previousTranslationValue, { + toValue: - this.state.dimensions.width / 2, + easing: Animated.Easing.InOut(), + duration: nextProps.duration, + useNativeDriver: true, + }) + ]); + compositeAnimation.start(() => this.onFinishedAnimation()); + }); + } + + popTransition(nextProps: TransitionContainerProps) { + const currentTranslationValue = Animated.createValue(- this.state.dimensions.width / 2 ); + const previousTranslationValue = Animated.createValue(0); + this.setState({ + previousChildren: this.props.children, + childrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 0, + transform: [{ translateX: currentTranslationValue }] + }), + previousChildrenAnimation: Styles.createAnimatedViewStyle({ + pointerEvents: 'none', + zIndex: 1, + transform: [{ translateX: previousTranslationValue }] + }), + }, () => { + const compositeAnimation = Animated.parallel([ + Animated.timing(currentTranslationValue, { + toValue: 0, + easing: Animated.Easing.InOut(), + duration: nextProps.duration, + useNativeDriver: true, + }), + Animated.timing(previousTranslationValue, { + toValue: this.state.dimensions.width, + easing: Animated.Easing.InOut(), + duration: nextProps.duration, + useNativeDriver: true, + }) + ]); + compositeAnimation.start(() => this.onFinishedAnimation()); + }); + } + + render() { + const { children } = this.props; + const { previousChildren, childrenAnimation, previousChildrenAnimation } = this.state; + return ( + <View style={{flex:1}}> + + { previousChildren && + (<Animated.View style={[this.state.animationStyles.style, previousChildrenAnimation]}> + { previousChildren } + </Animated.View>) } + + <Animated.View style={[this.state.animationStyles.style, childrenAnimation]}> + { children } + </Animated.View> + + </View> + ); + } +}
\ No newline at end of file diff --git a/app/routes.js b/app/routes.js index f9917f17bf..b5e1f8aa07 100644 --- a/app/routes.js +++ b/app/routes.js @@ -2,7 +2,7 @@ import * as React from 'react'; import { Switch, Route, Redirect } from 'react-router'; -import { CSSTransitionGroup } from 'react-transition-group'; +import TransitionContainer from './components/TransitionContainer'; import PlatformWindow from './components/PlatformWindow'; import LoginPage from './containers/LoginPage'; import ConnectPage from './containers/ConnectPage'; @@ -92,7 +92,7 @@ export default function makeRoutes(getState: ReduxGetState, componentProps: Shar return ( <PlatformWindow> - <CSSTransitionGroup component="div" className="transition-container" { ...transitionProps }> + <TransitionContainer { ...transitionProps }> <Switch key={ location.key } location={ location }> <LoginRoute exact path="/" component={ LoginPage } /> <PrivateRoute exact path="/connect" component={ ConnectPage } /> @@ -103,7 +103,7 @@ export default function makeRoutes(getState: ReduxGetState, componentProps: Shar <PublicRoute exact path="/settings/support" component={ SupportPage } /> <PrivateRoute exact path="/select-location" component={ SelectLocationPage } /> </Switch> - </CSSTransitionGroup> + </TransitionContainer> </PlatformWindow> ); }} /> diff --git a/app/transitions.js b/app/transitions.js index 58e692f99c..3e825c2968 100644 --- a/app/transitions.js +++ b/app/transitions.js @@ -1,16 +1,10 @@ // @flow - import TransitionRule from './lib/transition-rule'; import type { TransitionFork, TransitionDescriptor } from './lib/transition-rule'; -export type CSSTransitionGroupProps = { - transitionName: string, - transitionEnterTimeout: number, - transitionLeaveTimeout: number, - transitionEnter: boolean, - transitionLeave: boolean, - transitionAppear?: boolean, - transitionAppearTimeout?: number +export type TransitionGroupProps = { + name: string, + duration: number, }; type TransitionMap = { @@ -23,21 +17,21 @@ type TransitionMap = { const transitions: TransitionMap = { slide: { forward: { - name: 'slide-up-transition', + name: 'slide-up', duration: 450 }, backward: { - name: 'slide-down-transition', + name: 'slide-down', duration: 450 } }, push: { forward: { - name: 'push-transition', + name: 'push', duration: 450 }, backward: { - name: 'pop-transition', + name: 'pop', duration: 450 } } @@ -57,12 +51,12 @@ const transitionRules = [ ]; /** - * Calculate CSSTransitionGroup props. + * Calculate TransitionGroup props. * * @param {string} [fromRoute] - source route * @param {string} toRoute - target route */ -export function getTransitionProps(fromRoute: ?string, toRoute: string): CSSTransitionGroupProps { +export function getTransitionProps(fromRoute: ?string, toRoute: string): TransitionGroupProps { // ignore initial transition and transition between the same routes if(!fromRoute || fromRoute === toRoute) { return noTransitionProps(); @@ -71,7 +65,7 @@ export function getTransitionProps(fromRoute: ?string, toRoute: string): CSSTran for(const rule of transitionRules) { const match = rule.match(fromRoute, toRoute); if(match) { - return toCSSTransitionGroupProps(match.descriptor); + return toTransitionGroupProps(match.descriptor); } } @@ -79,30 +73,24 @@ export function getTransitionProps(fromRoute: ?string, toRoute: string): CSSTran } /** - * Integrate TransitionDescriptor into CSSTransitionGroupProps + * Integrate TransitionDescriptor into TransitionGroupProps * @param {TransitionDescriptor} descriptor */ -function toCSSTransitionGroupProps(descriptor: TransitionDescriptor): CSSTransitionGroupProps { +function toTransitionGroupProps(descriptor: TransitionDescriptor): TransitionGroupProps { const {name, duration} = descriptor; return { - transitionName: name, - transitionEnterTimeout: duration, - transitionLeaveTimeout: duration, - transitionEnter: true, - transitionLeave: true + name: name, + duration: duration, }; } /** - * Returns default props with animations disabled + * Returns default props with no animation */ -function noTransitionProps(): CSSTransitionGroupProps { +function noTransitionProps(): TransitionGroupProps { return { - transitionName: '', - transitionEnterTimeout: 0, - transitionLeaveTimeout: 0, - transitionEnter: false, - transitionLeave: false + name: '', + duration: 0, }; } |
