Skip to content
This repository was archived by the owner on Apr 14, 2020. It is now read-only.

Commit a8b6541

Browse files
authored
Merge pull request #91 from madou/dynamic-duration
Dynamic duration
2 parents cd5e49b + 1f24085 commit a8b6541

13 files changed

Lines changed: 312 additions & 48 deletions

File tree

.eslintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"react/prefer-stateless-function": "never",
2020
"react/sort-comp": "off",
2121
"react/destructuring-assignment": "never",
22+
"import/prefer-default-export": "off",
2223
"import/no-extraneous-dependencies": [
2324
"error",
2425
{ "devDependencies": ["**/*test.tsx", "**/*stories.tsx"] }

packages/yubaba/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
},
1414
"size-limit": [
1515
{
16-
"limit": "5.4 KB",
16+
"limit": "5.7 KB",
1717
"path": "dist/es6/packages/yubaba/src/index.js"
1818
}
1919
],

packages/yubaba/src/animations/CircleExpand/index.tsx

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import {
1010
calculateWindowCentre,
1111
calculateElementCenterInViewport,
1212
recalculateElementBoundingBoxFromScroll,
13+
getWindowDimensions,
1314
} from '../../lib/dom';
1415
import SimpleKeyframe from '../SimpleKeyframe';
1516
import { standard, accelerate } from '../../lib/curves';
1617
import { zIndexStack } from '../../lib/style';
18+
import { dynamic } from '../../lib/duration';
19+
import { Duration } from '../types';
1720

1821
export interface CircleExpandProps extends CollectorChildrenProps {
1922
/**
@@ -24,7 +27,7 @@ export interface CircleExpandProps extends CollectorChildrenProps {
2427
/**
2528
* How long the animation should take over {duration}ms.
2629
*/
27-
duration: number;
30+
duration: Duration;
2831

2932
/**
3033
* zIndex to be applied to the moving element.
@@ -43,48 +46,54 @@ export interface CircleExpandProps extends CollectorChildrenProps {
4346
*/
4447
export default class CircleExpand extends React.Component<CircleExpandProps> {
4548
static defaultProps = {
46-
duration: 500,
49+
duration: 'dynamic',
4750
zIndex: zIndexStack.circleExpand,
4851
};
4952

5053
renderAnimation = (data: AnimationData, options: { step?: number; onFinish: () => void }) => {
5154
const { duration, background, zIndex } = this.props;
5255

5356
// Scroll could have changed between unmount and this prepare step, let's recalculate just in case.
54-
const fromTargetSizeLocation = recalculateElementBoundingBoxFromScroll(
57+
const originBoundingBox = recalculateElementBoundingBoxFromScroll(
5558
data.origin.elementBoundingBox
5659
);
57-
const minSize = Math.min(fromTargetSizeLocation.size.width, fromTargetSizeLocation.size.height);
58-
const fromTargetHypotenuse = calculateHypotenuse(fromTargetSizeLocation.size);
59-
const fromTargetCenterInViewport = calculateElementCenterInViewport(fromTargetSizeLocation);
60+
const minSize = Math.min(originBoundingBox.size.width, originBoundingBox.size.height);
61+
const fromTargetHypotenuse = calculateHypotenuse(originBoundingBox.size);
62+
const fromTargetCenterInViewport = calculateElementCenterInViewport(originBoundingBox);
6063
const viewportCenter = calculateWindowCentre();
61-
const windowHypotenuse = calculateHypotenuse({
62-
width: window.innerWidth,
63-
height: window.innerHeight,
64-
});
64+
const windowDimensions = getWindowDimensions();
65+
const windowHypotenuse = calculateHypotenuse(windowDimensions);
6566
const difference = {
6667
width: viewportCenter.left - fromTargetCenterInViewport.left,
6768
height: viewportCenter.top - fromTargetCenterInViewport.top,
6869
};
6970
const hypotenuseDifference = calculateHypotenuse(difference);
7071
const scale = Math.ceil((windowHypotenuse + hypotenuseDifference) / minSize);
72+
const calculatedDuration =
73+
duration === 'dynamic'
74+
? dynamic(originBoundingBox, {
75+
location: { left: 0, top: 0 },
76+
size: windowDimensions,
77+
raw: {} as any,
78+
})
79+
: duration;
7180

7281
return (
7382
<SimpleKeyframe
7483
style={{
7584
zIndex,
7685
left:
77-
fromTargetSizeLocation.location.left -
78-
(fromTargetHypotenuse - fromTargetSizeLocation.size.width) / 2,
86+
originBoundingBox.location.left -
87+
(fromTargetHypotenuse - originBoundingBox.size.width) / 2,
7988
top:
80-
fromTargetSizeLocation.location.top -
81-
(fromTargetHypotenuse - fromTargetSizeLocation.size.height) / 2,
89+
originBoundingBox.location.top -
90+
(fromTargetHypotenuse - originBoundingBox.size.height) / 2,
8291
width: fromTargetHypotenuse,
8392
height: fromTargetHypotenuse,
8493
borderRadius: '50%',
8594
position: 'absolute',
8695
background,
87-
transition: `transform ${accelerate()} ${duration}ms, opacity ${standard()} ${duration /
96+
transition: `transform ${accelerate()} ${calculatedDuration}ms, opacity ${standard()} ${calculatedDuration /
8897
2}ms`,
8998
transform: 'scale(1)',
9099
willChange: 'transform',

packages/yubaba/src/animations/CircleShrink/index.tsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ import Collector, {
66
AnimationData,
77
} from '../../Collector';
88
import { calculateHypotenuse } from '../../lib/math';
9-
import { calculateWindowCentre, calculateElementCenterInViewport } from '../../lib/dom';
9+
import {
10+
calculateWindowCentre,
11+
calculateElementCenterInViewport,
12+
getWindowDimensions,
13+
} from '../../lib/dom';
1014
import SimpleKeyframe from '../SimpleKeyframe';
1115
import { standard, decelerate } from '../../lib/curves';
1216
import { zIndexStack } from '../../lib/style';
17+
import { dynamic } from '../../lib/duration';
18+
import { Duration } from '../types';
1319

1420
export interface CircleShrinkProps extends CollectorChildrenProps {
1521
/**
@@ -20,7 +26,7 @@ export interface CircleShrinkProps extends CollectorChildrenProps {
2026
/**
2127
* How long the animation should take over {duration}ms.
2228
*/
23-
duration: number;
29+
duration: Duration;
2430

2531
/**
2632
* zIndex to be applied to the moving element.
@@ -39,7 +45,7 @@ export interface CircleShrinkProps extends CollectorChildrenProps {
3945
*/
4046
export default class CircleShrink extends React.Component<CircleShrinkProps> {
4147
static defaultProps = {
42-
duration: 500,
48+
duration: 'dynamic',
4349
zIndex: zIndexStack.circleShrink,
4450
};
4551

@@ -55,16 +61,22 @@ export default class CircleShrink extends React.Component<CircleShrinkProps> {
5561
data.destination.elementBoundingBox
5662
);
5763
const viewportCenter = calculateWindowCentre();
58-
const windowHypotenuse = calculateHypotenuse({
59-
width: window.innerWidth,
60-
height: window.innerHeight,
61-
});
64+
const windowDimensions = getWindowDimensions();
65+
const windowHypotenuse = calculateHypotenuse(windowDimensions);
6266
const difference = {
6367
width: viewportCenter.left - toTargetCenterInViewport.left,
6468
height: viewportCenter.top - toTargetCenterInViewport.top,
6569
};
6670
const hypotenuseDifference = calculateHypotenuse(difference);
6771
const scale = Math.ceil((windowHypotenuse + hypotenuseDifference) / minSize);
72+
const calculatedDuration =
73+
duration === 'dynamic'
74+
? dynamic(data.destination.elementBoundingBox, {
75+
location: { left: 0, top: 0 },
76+
size: windowDimensions,
77+
raw: {} as any,
78+
})
79+
: duration;
6880

6981
return (
7082
<SimpleKeyframe
@@ -82,7 +94,7 @@ export default class CircleShrink extends React.Component<CircleShrinkProps> {
8294
position: 'absolute',
8395
background,
8496
willChange: 'transform',
85-
transition: `transform ${decelerate()} ${duration}ms, opacity ${standard()} ${duration}ms`,
97+
transition: `transform ${decelerate()} ${calculatedDuration}ms, opacity ${standard()} ${calculatedDuration}ms`,
8698
transform: `scale(${scale})`,
8799
}}
88100
keyframes={[

packages/yubaba/src/animations/ConcealMove/index.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import { recalculateElementBoundingBoxFromScroll } from '../../lib/dom';
1010
import noop from '../../lib/noop';
1111
import { standard } from '../../lib/curves';
1212
import { zIndexStack } from '../../lib/style';
13+
import { dynamic } from '../../lib/duration';
14+
import { Duration } from '../types';
1315

1416
export interface ConcealMoveProps extends CollectorChildrenProps {
1517
/**
1618
* How long the animation should take over {duration}ms.
1719
*/
18-
duration: number;
20+
duration: Duration;
1921

2022
/**
2123
* zIndex to be applied to the moving element.
@@ -37,11 +39,13 @@ export interface ConcealMoveProps extends CollectorChildrenProps {
3739
*/
3840
export default class ConcealMove extends React.Component<ConcealMoveProps> {
3941
static defaultProps = {
40-
duration: 500,
42+
duration: 'dynamic',
4143
timingFunction: standard(),
4244
zIndex: zIndexStack.concealMove,
4345
};
4446

47+
calculatedDuration: number = 0;
48+
4549
renderAnimation = (
4650
data: AnimationData,
4751
options: { moveToTarget?: boolean; fadeOut?: boolean } = {}
@@ -56,14 +60,20 @@ targetElement was missing.`);
5660
const fromTargetSizeLocation = recalculateElementBoundingBoxFromScroll(
5761
data.origin.elementBoundingBox
5862
);
63+
this.calculatedDuration =
64+
duration === 'dynamic'
65+
? dynamic(fromTargetSizeLocation, data.destination.elementBoundingBox)
66+
: duration;
5967

6068
return data.origin.render({
6169
ref: noop,
6270
style: {
6371
zIndex,
6472
opacity: options.fadeOut ? 0 : 1,
65-
transition: `transform ${duration}ms ${timingFunction}, height ${duration}ms ${timingFunction}, width ${duration}ms ${timingFunction}, opacity ${duration /
66-
2}ms ${timingFunction}`,
73+
transition: `transform ${this.calculatedDuration}ms ${timingFunction}, height ${
74+
this.calculatedDuration
75+
}ms ${timingFunction}, width ${this.calculatedDuration}ms ${timingFunction}, opacity ${this
76+
.calculatedDuration / 2}ms ${timingFunction}`,
6777
position: 'absolute',
6878
transformOrigin: '0 0',
6979
willChange: 'transform, height, width',
@@ -85,7 +95,7 @@ targetElement was missing.`);
8595
className: options.moveToTarget
8696
? css`
8797
> * {
88-
transition: transform ${duration}ms ${timingFunction};
98+
transition: transform ${this.calculatedDuration}ms ${timingFunction};
8999
transform: translate3d(
90100
-${data.origin.focalTargetElementBoundingBox.location.left - data.origin.elementBoundingBox.location.left}px,
91101
-${data.origin.focalTargetElementBoundingBox.location.top - data.origin.elementBoundingBox.location.top}px,
@@ -103,9 +113,7 @@ targetElement was missing.`);
103113
};
104114

105115
animate: AnimationCallback = (data, onFinish) => {
106-
const { duration } = this.props;
107-
108-
setTimeout(onFinish, duration);
116+
setTimeout(onFinish, this.calculatedDuration);
109117

110118
return this.renderAnimation(data, { moveToTarget: true });
111119
};

packages/yubaba/src/animations/FadeMove/index.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import { recalculateElementBoundingBoxFromScroll } from '../../lib/dom';
1010
import noop from '../../lib/noop';
1111
import { standard } from '../../lib/curves';
1212
import { zIndexStack } from '../../lib/style';
13+
import { dynamic } from '../../lib/duration';
14+
import { Duration } from '../types';
1315

1416
export interface FadeMoveProps extends CollectorChildrenProps {
1517
/**
1618
* How long the animation should take over {duration}ms.
1719
*/
18-
duration: number;
20+
duration: Duration;
1921

2022
/**
2123
* zIndex to be applied to the moving element.
@@ -42,11 +44,13 @@ export interface FadeMoveProps extends CollectorChildrenProps {
4244
*/
4345
export default class FadeMove extends React.Component<FadeMoveProps> {
4446
static defaultProps = {
45-
duration: 500,
47+
duration: 'dynamic',
4648
timingFunction: standard(),
4749
zIndex: zIndexStack.fadeMove,
4850
};
4951

52+
calculatedDuration: number = 0;
53+
5054
renderAnimation = (data: AnimationData, options: { moveToTarget?: boolean } = {}) => {
5155
const { timingFunction, duration, zIndex } = this.props;
5256
// Scroll could have changed between unmount and this prepare step, let's recalculate
@@ -58,14 +62,18 @@ export default class FadeMove extends React.Component<FadeMoveProps> {
5862
data.destination.elementBoundingBox.location.left - fromTargetSizeLocation.location.left;
5963
const fromEndYOffset =
6064
data.destination.elementBoundingBox.location.top - fromTargetSizeLocation.location.top;
65+
this.calculatedDuration =
66+
duration === 'dynamic'
67+
? dynamic(fromTargetSizeLocation, data.destination.elementBoundingBox)
68+
: duration;
6169

6270
return data.origin.render({
6371
ref: noop,
6472
style: {
6573
...fromTargetSizeLocation.location,
6674
zIndex,
67-
transition: `transform ${duration}ms ${timingFunction}, opacity ${duration /
68-
2}ms ${timingFunction}`,
75+
transition: `transform ${this.calculatedDuration}ms ${timingFunction}, opacity ${this
76+
.calculatedDuration / 2}ms ${timingFunction}`,
6977
position: 'absolute',
7078
transformOrigin: '0 0',
7179
transform: 'translate3d(0, 0, 0) scale3d(1, 1, 1)',
@@ -96,8 +104,7 @@ export default class FadeMove extends React.Component<FadeMoveProps> {
96104
};
97105

98106
animate: AnimationCallback = (data, onFinish) => {
99-
const { duration } = this.props;
100-
setTimeout(onFinish, duration);
107+
setTimeout(onFinish, this.calculatedDuration);
101108
return this.renderAnimation(data, { moveToTarget: true });
102109
};
103110

packages/yubaba/src/animations/Move/index.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import * as math from '../../lib/math';
88
import { recalculateElementBoundingBoxFromScroll } from '../../lib/dom';
99
import { standard } from '../../lib/curves';
1010
import { combine, zIndexStack } from '../../lib/style';
11+
import { Duration } from '../types';
12+
import { dynamic } from '../../lib/duration';
1113

1214
export interface MoveProps extends CollectorChildrenProps {
1315
/**
1416
* How long the animation should take over {duration}ms.
1517
*/
16-
duration: number;
18+
duration: Duration;
1719

1820
/**
1921
* zIndex to be applied to the moving element.
@@ -58,7 +60,7 @@ export interface MoveProps extends CollectorChildrenProps {
5860
*/
5961
export default class Move extends React.Component<MoveProps> {
6062
static defaultProps = {
61-
duration: 500,
63+
duration: 'dynamic',
6264
timingFunction: standard(),
6365
zIndex: zIndexStack.move,
6466
useFocalTarget: false,
@@ -108,20 +110,26 @@ targetElement was missing.`);
108110
onFinish();
109111
};
110112

111-
animate: AnimationCallback = (_, onFinish, setChildProps) => {
113+
animate: AnimationCallback = (data, onFinish, setChildProps) => {
112114
const { duration, timingFunction } = this.props;
113115

116+
const calculatedDuration =
117+
duration === 'dynamic'
118+
? dynamic(data.origin.elementBoundingBox, data.destination.elementBoundingBox)
119+
: duration;
120+
114121
setChildProps({
115122
style: prevStyles => ({
116123
...prevStyles,
117124
transition: combine(
118-
`transform ${duration}ms ${timingFunction}, opacity ${duration / 2}ms ${timingFunction}`
125+
`transform ${calculatedDuration}ms ${timingFunction}, opacity ${calculatedDuration /
126+
2}ms ${timingFunction}`
119127
)(prevStyles.transition),
120128
transform: 'translate3d(0, 0, 0) scale3d(1, 1, 1)',
121129
}),
122130
});
123131

124-
setTimeout(() => onFinish(), duration);
132+
setTimeout(() => onFinish(), calculatedDuration);
125133
};
126134

127135
render() {

0 commit comments

Comments
 (0)