1+ import { isArray , mapKeys , mapValues , pickBy } from 'lodash'
12import stepper from './stepper'
2- import { mapValues } from './util '
3- import { getAnimatableProps } from './props '
3+ import { combine } from './values '
4+ import { getAnimatableProps } from './parse '
45
6+ // spring presets. selected combinations of stiffness/damping.
57const presets = {
68 noWobble : { stiffness : 170 , damping : 26 } ,
79 gentle : { stiffness : 120 , damping : 14 } ,
@@ -11,15 +13,51 @@ const presets = {
1113
1214// default spring options.
1315// damping and precision reflect the values of the `wobbly` preset,
14- // precision defaults to 3 which should be a good tradeoff between
16+ // precision defaults to 2 which should be a good tradeoff between
1517// animation detail and resulting filesize.
1618const defaultOptions = {
1719 stiffness : 180 ,
1820 damping : 12 ,
19- precision : 3 ,
21+ precision : 2 ,
22+ }
23+
24+ export const appendUnits = ( object , units ) =>
25+ mapValues ( object , ( value , key ) => {
26+ return isArray ( value )
27+ ? value . map ( ( value , i ) => `${ value } ${ units [ key ] [ i ] } ` )
28+ : `${ value } ${ units [ key ] } `
29+ } )
30+
31+ // @todo comment
32+ const buildInterpolation = ( stiffness , damping ) => {
33+ return ( start , end ) => {
34+ const interpolated = [ ]
35+ const interpolateArray = isArray ( start )
36+ let value = interpolateArray ? start [ 0 ] : start
37+ let velocity = 0
38+
39+ for ( let i = 1 ; i < 100 ; i += 1 ) {
40+ if ( interpolateArray ) {
41+ let something
42+ for ( let j = 0 ; j < start . length ; j += 1 ) {
43+ something = [ ] ;
44+ [ value , velocity ] = stepper ( 0.01 , value , velocity , end [ j ] , stiffness , damping )
45+ something . push ( value )
46+ }
47+ interpolated . push ( something )
48+ } else {
49+ [ value , velocity ] = stepper ( 0.01 , value , velocity , end , stiffness , damping )
50+ interpolated . push ( value )
51+ }
52+ }
53+
54+ return [ ] . concat ( start , interpolated , end )
55+ }
2056}
2157
2258export const spring = ( startProps , endProps , options = { } ) => {
59+ let result = { }
60+
2361 // define stiffness, damping and precision based on default options
2462 // and options given in arguments.
2563 const { stiffness, damping, precision } = Object . assign (
@@ -29,51 +67,65 @@ export const spring = (startProps, endProps, options = {}) => {
2967 presets [ options . preset ] || { }
3068 )
3169
32- const animatableProps = getAnimatableProps ( startProps , endProps )
33- const startValues = mapValues ( animatableProps , ( { start } ) => start )
34- const endValues = mapValues ( animatableProps , ( { end } ) => end )
70+ // build an interpolation function based on the given stiffness and
71+ // damping values
72+ const interpolate = buildInterpolation ( stiffness , damping )
3573
36- const addUnits = ( object ) =>
37- mapValues ( object , ( v , k ) => `${ v } ${ animatableProps [ k ] . unit } ` )
74+ // @todo comment!
75+ const data = getAnimatableProps ( startProps , endProps )
76+ data . forEach ( ( { prop, unit, start, end } ) => {
77+ interpolate ( start , end ) . forEach ( ( interpolated , i ) => {
78+ // round to desired precision (except when interpolating pixels)
79+ const value = Number ( interpolated . toFixed ( unit === 'px' ? 0 : precision ) )
80+ // when the value is 0, there's no need to add a unit.
81+ const valueWithUnit = value === 0 ? value : `${ value } ${ unit } `
3882
39- const keyframes = {
40- '0%' : addUnits ( startValues ) ,
41- '100%' : addUnits ( endValues ) ,
42- }
43-
44- Object . keys ( startValues ) . forEach ( ( key ) => {
45- let velocity = 0
46- let value = startValues [ key ]
47- const end = endValues [ key ]
83+ if ( result [ i ] === undefined ) {
84+ result [ i ] = { [ prop ] : valueWithUnit }
85+ } else {
86+ result [ i ] [ prop ] = result [ i ] [ prop ] === undefined
87+ ? valueWithUnit
88+ : [ ] . concat ( result [ i ] [ prop ] , valueWithUnit )
89+ }
90+ } )
91+ } )
4892
49- for ( let i = 1 ; i < 100 ; i += 1 ) {
50- [ value , velocity ] = stepper ( 0.01 , value , velocity , end , stiffness , damping )
51- const percent = `${ i } %`
52- keyframes [ percent ] = Object . assign (
53- keyframes [ percent ] || { } ,
54- { [ key ] : `${ Number ( value . toFixed ( precision ) ) } ${ animatableProps [ key ] . unit } ` }
55- )
93+ // iterate over the result object, combining values and identifying
94+ // equal frames to be able to eliminate duplicates in a later step
95+ let prevFrame
96+ const obsoleteFrames = [ ]
97+ Object . keys ( result ) . forEach ( ( i ) => {
98+ const currentFrame = JSON . stringify ( result [ i ] )
99+ result [ i ] = mapValues ( result [ i ] , ( value , key ) => combine ( key , value ) )
100+ if ( prevFrame === currentFrame ) {
101+ obsoleteFrames . push ( i - 1 )
56102 }
103+ prevFrame = currentFrame
57104 } )
58105
59- return keyframes
106+ // remove obsolute frames to reduce size and add % to keys
107+ // @todo might chain this - not using chain.
108+ // @see https://medium.com/making-internets/why-using-chain-is-a-mistake-9bc1f80d51ba
109+ result = mapKeys (
110+ pickBy ( result , ( value , key ) => obsoleteFrames . indexOf ( Number ( key ) ) < 0 ) ,
111+ ( value , key ) => `${ key } %`
112+ )
113+
114+ console . log ( result )
115+ return result
60116}
61117
62- // console.log(spring({
63- // left: '10px',
64- // right: '20em',
65- // foo: 'bar',
66- // opacity: 0,
67- // rotate: '5deg'
68- // }, {
69- // left: '20px',
70- // right: '30em',
71- // baz: true,
72- // opacity: 1,
73- // rotate: '10deg'
74- // }, {
75- // preset: 'noWobble'
76- // }))
118+ spring ( {
119+ left : '10px' ,
120+ right : '20px' ,
121+ padding : '0 0 10px 10rem' ,
122+ } , {
123+ left : '20px' ,
124+ right : 0 ,
125+ padding : '10em 10em 0 20rem' ,
126+ } , {
127+ preset : 'noWobble' ,
128+ } )
77129
78130export { default as toString } from './to-string'
79131export default spring
0 commit comments