11// flow
22import type { Animation , Keyframe , CSSValue } from './types' ;
33
4+ // Keyframes that bound an animation
5+ const boundryFrmes : { [ frame : string ] : boolean } = {
6+ 'from' : true ,
7+ '0%' : true ,
8+ 'to' : true ,
9+ '100%' : true ,
10+ }
11+
12+ // The default allowed delta for keyframe distance
13+ const keyframeDistance = 10 ;
14+
415/**
516 * Merge lets you take two Animations and merge them together. It
617 * iterates through each animation and merges each keyframe. It
@@ -14,33 +25,105 @@ import type { Animation, Keyframe, CSSValue } from './types';
1425 * import { merge, tada, flip } from 'react-effects';
1526 * const tadaFlip = merge(tada, flip);
1627 */
17- export default function merge ( a : Animation , b : Animation ) : Animation {
18- const merged : Animation = { } ;
19- // Copy all values from a
20- for ( let frame in a ) {
21- const sourceFrame : Keyframe = a [ frame ] ;
22- const targetFrame : Keyframe = b [ frame ] ;
23- const target : Keyframe = merged [ frame ] || ( merged [ frame ] = { } ) ;
24- for ( let cssValue : string in sourceFrame ) {
25- if ( cssValue === 'transform' && targetFrame && targetFrame [ cssValue ] ) {
26- var transforms = [ sourceFrame [ cssValue ] , targetFrame [ cssValue ] ]
27- target [ cssValue ] = transforms . filter (
28- transform => transform !== 'none'
29- )
30- . join ( ' ' )
31- . trim ( ) ;
28+ export default function merge ( primary , secondary ) {
29+ // A map used to track the normalized frame value in cases where
30+ // two animations contain frames that appear closely, but not exactly
31+ // at the same time (e.g., 50% and 52%)
32+ const normalizedFrames = {
33+ 'from' : 'from' ,
34+ '0%' : 'from' ,
35+ 'to' : 'to' ,
36+ '100%' : 'to' ,
37+ } ;
38+
39+
40+ // If we are dealing with an animation that appears to be
41+ // a "boundry-specific" animation, meaning it only specifies
42+ // a start and end position, we want to persist the start transform
43+ // throughout.
44+ let boundryTransform = null ;
45+ // We merge each frame into a new object and return it
46+ const merged = { } ;
47+ /* primary frame should control directional movement */
48+ const primaryFrames = Object . keys ( primary ) ;
49+ /* secondary frames should control orientation/size */
50+ const secondaryFrames = Object . keys ( secondary ) ;
51+
52+ const normalizedPrimary = cacheNormalizedFrames (
53+ primary ,
54+ normalizedFrames
55+ ) ;
56+
57+ const normalizedSecondary = cacheNormalizedFrames (
58+ secondary ,
59+ normalizedFrames
60+ ) ;
61+
62+ // We parse a boundry transform from either the primary
63+ // or secondary animation, if either look to be bounded.
64+ // Primary animation is given precedence.
65+ if ( secondaryFrames . length <= 2 ) {
66+ boundryTransform = parseBoundryTransform (
67+ normalizedSecondary
68+ ) ;
69+ } else if ( primaryFrames . length <= 2 ) {
70+ boundryTransform = parseBoundryTransform (
71+ primaryFrames
72+ ) ;
73+ }
74+
75+ // Iterate through all the cached, normalized animation frames.
76+ for ( let frame in normalizedFrames ) {
77+ const primaryFrame = normalizedPrimary [ frame ] ;
78+ const secondaryFrame = normalizedSecondary [ frame ] ;
79+ const target = merged [ frame ] || ( merged [ frame ] = { } ) ;
80+
81+ for ( let propertyName in primaryFrame ) {
82+ if ( propertyName === 'transform' && secondaryFrame ) {
83+ // TODO we should only apply the boundry transform when we
84+ // are in between boundry frames, not in them. We need to track
85+ // what the actual start and end frame are.
86+ target [ propertyName ] = mergeTransforms ( [
87+ primaryFrame [ propertyName ] ,
88+ secondaryFrame [ propertyName ] ,
89+ boundryTransform
90+ ] ) ;
3291 } else {
33- target [ cssValue ] = sourceFrame [ cssValue ]
92+ target [ propertyName ] = primaryFrame [ propertyName ] ;
93+ }
94+ }
95+
96+ for ( let propertyName in secondaryFrame ) {
97+ if ( ! target [ propertyName ] ) {
98+ target [ propertyName ] = secondaryFrame [ propertyName ] ;
3499 }
35100 }
36- }
37- // Copy all values from b
38- for ( let frame in b ) {
39- const sourceFrame : Keyframe = b [ frame ] ;
40- const target : Keyframe = merged [ frame ] || ( merged [ frame ] = { } ) ;
41- for ( let cssValue in sourceFrame ) {
42- target [ cssValue ] = target [ cssValue ] || sourceFrame [ cssValue ] ;
43- } ;
44101 }
45102 return merged ;
46103}
104+
105+ function mergeTransforms ( transforms ) {
106+ transforms = transforms . filter (
107+ transform => transform && transform !== 'none'
108+ )
109+ transforms = transforms . join ( ' ' ) ;
110+ return transforms . trim ( ) ;
111+ }
112+
113+
114+ function parseBoundryTransform ( animation ) {
115+ return animation . from && animation . from . transform ;
116+ }
117+
118+
119+ function cacheNormalizedFrames ( source , cache ) {
120+ const normalized = { } ;
121+ for ( let frame in source ) {
122+ const normalizedFrame = cache [ frame ] || ( Math . round (
123+ parseFloat ( frame ) / keyframeDistance
124+ ) * keyframeDistance ) + '%' ;
125+ normalized [ normalizedFrame ] = source [ frame ] ;
126+ cache [ normalizedFrame ] = normalizedFrame ;
127+ }
128+ return normalized ;
129+ }
0 commit comments