Skip to content

Commit d26d5ec

Browse files
author
Luke Brandon Farrell
authored
feat(animations): merge pull request #60 from CursedWizard/master
feat(animations): use transforms with translateX/translateY instead of dynamic margin.
2 parents 84824f9 + 7c4b4cf commit d26d5ec

File tree

2 files changed

+127
-80
lines changed

2 files changed

+127
-80
lines changed

src/RNNDrawer.tsx

100644100755
+123-78
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,14 @@ export enum DirectionType {
7878
top = 'top',
7979
}
8080

81+
interface Point {
82+
moveX: number;
83+
moveY: number;
84+
}
85+
8186
interface IState {
82-
sideMenuOpenValue: any;
83-
sideMenuOverlayOpacity: any;
87+
sideMenuOpenValue: Animated.Value;
88+
sideMenuOverlayOpacity: Animated.Value;
8489
sideMenuSwipingStarted: boolean;
8590
sideMenuIsDismissing: boolean;
8691
screenHeight: number;
@@ -97,6 +102,7 @@ interface IProps {
97102
fadeOpacity: number;
98103
drawerScreenWidth: number | string;
99104
drawerScreenHeight: number | string;
105+
animateDrawerExpanding?: boolean;
100106
style: any;
101107
}
102108

@@ -124,7 +130,7 @@ interface DrawerReverseDirectionInterface {
124130
}
125131

126132
interface SwipeMoveInterface {
127-
value: number;
133+
value: Point;
128134
direction: string;
129135
}
130136

@@ -142,13 +148,16 @@ class RNNDrawer {
142148
private readonly drawerWidth: number;
143149
private readonly drawerHeight: number;
144150
private readonly drawerOpenedValues: DrawerDirectionValuesInterface;
151+
private readonly initialValues: DrawerDirectionValuesInterface;
145152
private panResponder: PanResponderInstance;
146-
private animatedDrawer: any;
147-
private animatedOpacity: any;
148-
private unsubscribeSwipeStart: any;
149-
private unsubscribeSwipeMove: any;
150-
private unsubscribeSwipeEnd: any;
151-
private unsubscribeDismissDrawer: any;
153+
private animatedDrawer!: Animated.CompositeAnimation;
154+
private animatedOpacity!: Animated.CompositeAnimation;
155+
private unsubscribeSwipeStart!: () => void;
156+
private unsubscribeSwipeMove!: () => void;
157+
private unsubscribeSwipeEnd!: () => void;
158+
private unsubscribeDismissDrawer!: () => void;
159+
private panningStartedPoint: Point = { moveX: 0 , moveY: 0 };
160+
private startedFromSideMenu: boolean = false;
152161

153162
static defaultProps = {
154163
animationOpenTime: 300,
@@ -158,6 +167,7 @@ class RNNDrawer {
158167
fadeOpacity: 0.6,
159168
drawerScreenWidth: '80%',
160169
drawerScreenHeight: '100%',
170+
animateDrawerExpanding: true
161171
};
162172

163173
/**
@@ -186,9 +196,12 @@ class RNNDrawer {
186196
_evt: GestureResponderEvent,
187197
_gestureState: PanResponderGestureState,
188198
) => {
189-
const { dx } = _gestureState;
199+
const { dx, dy } = _gestureState;
190200

191-
return Math.abs(dx) > 5;
201+
if (this.props.direction === DirectionType.left || this.props.direction === DirectionType.right)
202+
return Math.abs(dx) > 5;
203+
else
204+
return Math.abs(dy) > 5;
192205
},
193206
onMoveShouldSetPanResponderCapture: (
194207
_evt: GestureResponderEvent,
@@ -198,7 +211,9 @@ class RNNDrawer {
198211
_evt: GestureResponderEvent,
199212
_gestureState: PanResponderGestureState,
200213
) => {
201-
dispatch('SWIPE_START');
214+
const { moveX, moveY } = _gestureState;
215+
216+
dispatch('SWIPE_START', {moveX, moveY});
202217
},
203218
onPanResponderRelease: (
204219
_evt: GestureResponderEvent,
@@ -221,10 +236,10 @@ class RNNDrawer {
221236
_evt: GestureResponderEvent,
222237
_gestureState: PanResponderGestureState,
223238
) => {
224-
const { moveX } = _gestureState;
239+
const { moveX, moveY } = _gestureState;
225240
const direction = this.props.direction || 'left';
226241

227-
dispatch('SWIPE_MOVE', { value: moveX, direction });
242+
dispatch('SWIPE_MOVE', { value: { moveX, moveY }, direction });
228243
},
229244
});
230245

@@ -263,20 +278,20 @@ class RNNDrawer {
263278
this.drawerOpenedValues = {
264279
left: 0,
265280
right: this.screenWidth - this.drawerWidth,
266-
top: 0,
281+
top: this.drawerHeight - this.screenHeight,
267282
bottom: this.screenHeight - this.drawerHeight,
268283
};
269284

270-
const initialValues: DrawerDirectionValuesInterface = {
285+
this.initialValues = {
271286
left: -this.drawerWidth,
272287
right: this.screenWidth,
273-
top: -this.drawerHeight,
288+
top: -this.screenHeight,
274289
bottom: this.screenHeight,
275290
};
276291

277292
/** Component State */
278293
this.state = {
279-
sideMenuOpenValue: new Animated.Value(initialValues[props.direction]),
294+
sideMenuOpenValue: new Animated.Value(this.initialValues[props.direction]),
280295
sideMenuOverlayOpacity: new Animated.Value(0),
281296
sideMenuSwipingStarted: false,
282297
sideMenuIsDismissing: false,
@@ -310,13 +325,16 @@ class RNNDrawer {
310325
*/
311326
componentDidMount() {
312327
/** Props */
313-
const { direction, fadeOpacity } = this.props;
328+
const { direction, fadeOpacity, animateDrawerExpanding } = this.props;
329+
330+
if (typeof animateDrawerExpanding !== 'undefined' && !animateDrawerExpanding)
331+
this.startedFromSideMenu = true;
314332

315333
// Animate side menu open
316334
this.animatedDrawer = Animated.timing(this.state.sideMenuOpenValue, {
317335
toValue: this.drawerOpenedValues[direction],
318336
duration: this.props.animationOpenTime,
319-
useNativeDriver: false,
337+
useNativeDriver: true,
320338
});
321339

322340
// Animate outside side menu opacity
@@ -325,7 +343,7 @@ class RNNDrawer {
325343
{
326344
toValue: fadeOpacity,
327345
duration: this.props.animationOpenTime,
328-
useNativeDriver: false,
346+
useNativeDriver: true,
329347
},
330348
);
331349
}
@@ -339,7 +357,7 @@ class RNNDrawer {
339357
this.registerListeners();
340358

341359
// If there has been no Swiping, and this component appears, then just start the open animations
342-
if (!this.state.sideMenuSwipingStarted) {
360+
if (!this.state.sideMenuSwipingStarted && this.props.animateDrawerExpanding) {
343361
this.animatedDrawer.start();
344362
this.animatedOpacity.start();
345363
}
@@ -356,6 +374,20 @@ class RNNDrawer {
356374
dispatch('DRAWER_CLOSED');
357375
}
358376

377+
onOrientationChange = ({window}: any) => {
378+
const screenHeight = window.height;
379+
380+
this.setState({ screenHeight });
381+
382+
// Apply correct position if opened from right
383+
if (this.props.direction === 'right') {
384+
// Calculates the position of the drawer from the left side of the screen
385+
const alignedMovementValue = window.width - this.drawerWidth;
386+
387+
this.state.sideMenuOpenValue.setValue(alignedMovementValue);
388+
}
389+
}
390+
359391
/**
360392
* Registers all the listenrs for this component
361393
*/
@@ -364,22 +396,13 @@ class RNNDrawer {
364396
const { direction, fadeOpacity } = this.props;
365397

366398
// Adapt the drawer's size on orientation change
367-
Dimensions.addEventListener('change', ({ window }) => {
368-
const screenHeight = window.height;
369-
370-
this.setState({ screenHeight });
371-
372-
// Apply correct position if opened from right
373-
if (this.props.direction === 'right') {
374-
// Calculates the position of the drawer from the left side of the screen
375-
const alignedMovementValue = window.width - this.drawerWidth;
376-
377-
this.state.sideMenuOpenValue.setValue(alignedMovementValue);
378-
}
379-
});
399+
Dimensions.addEventListener('change', this.onOrientationChange);
380400

381401
// Executes when the side of the screen interaction starts
382-
this.unsubscribeSwipeStart = listen('SWIPE_START', () => {
402+
this.unsubscribeSwipeStart = listen('SWIPE_START', (value: Point) => {
403+
this.panningStartedPoint.moveX = value.moveX;
404+
this.panningStartedPoint.moveY = value.moveY;
405+
383406
this.setState({
384407
sideMenuSwipingStarted: true,
385408
});
@@ -389,46 +412,62 @@ class RNNDrawer {
389412
this.unsubscribeSwipeMove = listen(
390413
'SWIPE_MOVE',
391414
({ value, direction: swipeDirection }: SwipeMoveInterface) => {
392-
if (swipeDirection === 'left') {
393-
// Calculates the position of the drawer from the left side of the screen
394-
const alignedMovementValue = value - this.drawerWidth;
395-
// Calculates the percetage 0 - 100 of which the drawer is open
396-
const openedPercentage = Math.abs(
397-
(Math.abs(alignedMovementValue) / this.drawerWidth) * 100 - 100,
398-
);
399-
// Calculates the opacity to set of the screen based on the percentage the drawer is open
400-
const normalizedOpacity = Math.min(
401-
openedPercentage / 100,
402-
fadeOpacity,
403-
);
404-
405-
// Does allow the drawer to go further than the maximum width
406-
if (this.drawerOpenedValues[direction] > alignedMovementValue) {
407-
// Sets the animation values, we use this so we can resume animation from any point
408-
this.state.sideMenuOpenValue.setValue(alignedMovementValue);
415+
// Cover special case when we are swiping from the edge of the screen
416+
if (this.startedFromSideMenu) {
417+
if (direction === "left" && value.moveX < this.drawerWidth) {
418+
this.state.sideMenuOpenValue.setValue(value.moveX - this.drawerWidth);
419+
const normalizedOpacity = Math.min(
420+
(value.moveX / this.drawerWidth) * fadeOpacity,
421+
fadeOpacity,
422+
);
409423
this.state.sideMenuOverlayOpacity.setValue(normalizedOpacity);
410424
}
425+
if (direction === "right" && (this.screenWidth - value.moveX) < this.drawerWidth) {
426+
this.state.sideMenuOpenValue.setValue(value.moveX);
427+
const normalizedOpacity = Math.min(
428+
((this.screenWidth - value.moveX) / this.drawerWidth) * fadeOpacity,
429+
fadeOpacity,
430+
);
431+
this.state.sideMenuOverlayOpacity.setValue(normalizedOpacity);
432+
}
433+
434+
return;
435+
}
436+
437+
// Calculates the translateX / translateY value
438+
let alignedMovementValue = 0;
439+
// To swap the direction if needed
440+
let directionModifier = 1;
441+
// Whether we use the height of the drawer or the width
442+
let drawerDimension = this.drawerWidth;
443+
444+
if (swipeDirection === 'left') {
445+
alignedMovementValue = value.moveX - this.panningStartedPoint.moveX;
411446
} else if (swipeDirection === 'right') {
412-
// Works out the distance from right of screen to the finger position
413-
const normalizedValue = this.screenWidth - value;
414-
// Calculates the position of the drawer from the left side of the screen
415-
const alignedMovementValue = this.screenWidth - normalizedValue;
416-
// Calculates the percetage 0 - 100 of which the drawer is open
417-
const openedPercentage = Math.abs(
418-
(Math.abs(normalizedValue) / this.drawerWidth) * 100,
419-
);
420-
// Calculates the opacity to set of the screen based on the percentage the drawer is open
421-
const normalizedOpacity = Math.min(
422-
openedPercentage / 100,
447+
alignedMovementValue = this.panningStartedPoint.moveX - value.moveX;
448+
directionModifier = -1;
449+
} else if (swipeDirection === 'bottom') {
450+
alignedMovementValue = this.panningStartedPoint.moveY - value.moveY;
451+
directionModifier = -1;
452+
drawerDimension = this.drawerHeight;
453+
} else if (swipeDirection === 'top') {
454+
alignedMovementValue = value.moveY - this.panningStartedPoint.moveY;
455+
drawerDimension = this.drawerHeight;
456+
}
457+
458+
// Calculates the percentage 0 - 1 of which the drawer is open
459+
const openedPercentage = Math.abs(drawerDimension + alignedMovementValue) / drawerDimension;
460+
// Calculates the opacity to set of the screen based on the percentage the drawer is open
461+
const normalizedOpacity = Math.min(
462+
openedPercentage * fadeOpacity,
423463
fadeOpacity,
424-
);
464+
);
425465

426-
// Does allow the drawer to go further than the maximum width
427-
if (this.drawerOpenedValues[direction] < alignedMovementValue) {
428-
// Sets the animation values, we use this so we can resume animation from any point
429-
this.state.sideMenuOpenValue.setValue(alignedMovementValue);
430-
this.state.sideMenuOverlayOpacity.setValue(normalizedOpacity);
431-
}
466+
// Does not allow the drawer to go further than the maximum width / height
467+
if (0 > alignedMovementValue) {
468+
// Sets the animation values, we use this so we can resume animation from any point
469+
this.state.sideMenuOpenValue.setValue(this.drawerOpenedValues[direction] + alignedMovementValue * directionModifier);
470+
this.state.sideMenuOverlayOpacity.setValue(normalizedOpacity);
432471
}
433472
},
434473
);
@@ -440,7 +479,11 @@ class RNNDrawer {
440479
const reverseDirection: DrawerReverseDirectionInterface = {
441480
right: 'left',
442481
left: 'right',
482+
top: 'bottom',
483+
bottom: 'top'
443484
};
485+
// In case the drawer started by dragging the edge of the screen reset the flag
486+
this.startedFromSideMenu = false;
444487

445488
if (swipeDirection === reverseDirection[direction]) {
446489
this.animatedDrawer.start();
@@ -472,7 +515,7 @@ class RNNDrawer {
472515
* Removes all the listenrs from this component
473516
*/
474517
removeListeners() {
475-
Dimensions.removeEventListener('change', () => {});
518+
Dimensions.removeEventListener('change', this.onOrientationChange);
476519
if (this.unsubscribeSwipeStart) this.unsubscribeSwipeStart();
477520
if (this.unsubscribeSwipeMove) this.unsubscribeSwipeMove();
478521
if (this.unsubscribeSwipeEnd) this.unsubscribeSwipeEnd();
@@ -494,8 +537,8 @@ class RNNDrawer {
494537
/** Variables */
495538
const animatedValue =
496539
direction === DirectionType.left || direction === DirectionType.right
497-
? { marginLeft: sideMenuOpenValue }
498-
: { marginTop: sideMenuOpenValue };
540+
? { translateX: sideMenuOpenValue }
541+
: { translateY: sideMenuOpenValue };
499542

500543
return (
501544
<View
@@ -517,7 +560,9 @@ class RNNDrawer {
517560
{
518561
height: this.state.screenHeight,
519562
width: this.drawerWidth,
520-
...animatedValue,
563+
transform:[
564+
animatedValue
565+
]
521566
},
522567
]}
523568
>
@@ -547,15 +592,15 @@ class RNNDrawer {
547592
const closeValues: DrawerDirectionValuesInterface = {
548593
left: -this.drawerWidth,
549594
right: this.screenWidth,
550-
top: -this.drawerHeight,
595+
top: -this.screenHeight,
551596
bottom: this.screenHeight,
552597
};
553598

554599
// Animate side menu close
555600
Animated.timing(this.state.sideMenuOpenValue, {
556601
toValue: closeValues[direction],
557602
duration: this.props.animationCloseTime,
558-
useNativeDriver: false,
603+
useNativeDriver: true,
559604
}).start(() => {
560605
Navigation.dismissOverlay(this.props.componentId);
561606
this.setState({ sideMenuIsDismissing: false });
@@ -565,7 +610,7 @@ class RNNDrawer {
565610
Animated.timing(this.state.sideMenuOverlayOpacity, {
566611
toValue: 0,
567612
duration: this.props.animationCloseTime,
568-
useNativeDriver: false,
613+
useNativeDriver: true,
569614
}).start();
570615
}
571616
}

0 commit comments

Comments
 (0)