import springer from 'springer' ; import { keyframes } from '@emotion/core' ; const defaults = { stiffness : 50 , damping : 80 , precision : 2 , unit : 'px' , } ; const transformMap = [ 'x' , 'y' , 'scale' ] ; function roundToPrecision ( num , precision = 2 ) { const decimalPoints = Math . pow ( 10 , precision ) ; return Math . ceil ( num * decimalPoints ) / decimalPoints ; } const calcPropTweenVal = ( prop , frame , from , to , { precision } ) => ( spring ) => { const value = ( from [ prop ] || 0 ) + ( ( to [ prop ] || 0 ) - ( from [ prop ] || 0 ) ) * spring ( frame / 100 ) ; return roundToPrecision ( value , precision ) ; } ; const createCalcPropTweenVal = ( from , to , options ) => ( prop , frame ) => { const spring = springer ( options . damping / 100 , options . stiffness / 100 ) ; return calcPropTweenVal ( prop , frame , from , to , options ) ( spring ) ; } ; const splitTransform = ( prop , value , transformList = [ ] ) => transformMap . includes ( prop ) ? { transform : [ ... transformList , [ prop , value ] ] } : { [ prop ] : value } ; const reduceFrame = ( tween , property , value ) => ( { ... tween , ... splitTransform ( property , value , tween . transform ) } ) ; function mapTransformPropToCss ( prop , sprungValue , unit = 'px' ) { switch ( prop ) { case 'y' : return `translateY(${ sprungValue }${ unit })` ; case 'x' : return `translateX(${ sprungValue }${ unit })` ; case 'scale' : return `scale3d(${ sprungValue }, ${ sprungValue }, 1)` ; default : return `${ prop }(${ sprungValue })` ; } } const mapTransformProps = ( sprungFrameTuples , unit ) => sprungFrameTuples . reduce ( ( transform , [ prop , spring ] ) => `${ transform } ${ mapTransformPropToCss ( prop , spring , unit ) }` , 'transform:' ) ; const mapPropTypes = ( prop , spring , unit ) => prop === 'transform' ? `${ mapTransformProps ( spring , unit ) };` : `${ prop }: ${ spring };` ; const mapToCss = ( spring , unit ) => Object . keys ( spring ) . reduce ( ( animation , prop ) => `${ animation }${ mapPropTypes ( prop , spring [ prop ] , unit ) }` , '' ) ; export function spring ( { from , to } , options ? ) { const { stiffness , damping , precision , unit } = { ... defaults , ... options , } ; const calcTween = createCalcPropTweenVal ( from , to , { stiffness , damping , precision , } ) ; const frames = new Array ( 101 ) . fill ( '' ) ; return frames . map ( ( _ , frame ) => [ Object . keys ( from ) . reduce ( ( tween , prop ) => reduceFrame ( tween , prop , calcTween ( prop , frame ) ) , { } ) , frame , ] ) . map ( ( [ sprungValues , frame ] ) => [ `${ frame }%` , mapToCss ( sprungValues , unit ) ] ) . filter ( ( [ frame , spring ] , i , frames ) => { const lastIndex = i - 1 > 0 ? i - 1 : 0 ; return lastIndex > 0 && frame !== '100%' ? frames [ lastIndex ] [ 1 ] !== spring : true ; } ) . map ( ( [ frame , spring ] ) => `${ frame } {${ spring }}` ) ; } export interface Props { from : Partial < Css > ; to : Partial < Css > ; } interface Css { x : number ; y : number ; scale : number ; opacity : number ; } interface Options { stiffness : number ; damping : number ; precision : number ; unit : string ; } export default function ( { from , to } , options ? ) { return keyframes ( spring ( { from , to } , options ) . join ( '' ) ) ; }