import { assert , isDefined } from '../util/util' ; import { Platform } from '../platform/platform' ; hidden export class Animation { private _c : Animation [ ] ; private _cL : number ; private _e : HTMLElement [ ] ; private _eL : number ; private _fx : EffectProperty [ ] ; private _dur : number = null ; private _es : string = null ; private _rvEs : string = null ; private _bfSty : { [ property ] : any ; } ; private _bfAdd : string [ ] ; private _bfRm : string [ ] ; private _afSty : { [ property ] : any ; } ; private _afAdd : string [ ] ; private _afRm : string [ ] ; private _rdFn : Function [ ] ; private _wrFn : Function [ ] ; private _fFn : Function [ ] ; private _fOneFn : Function [ ] ; private _rv : boolean ; private _unrgTrns : Function ; private _tm : number ; private _hasDur : boolean ; private _isAsync : boolean ; private _twn : boolean ; plt : Platform ; parent : Animation ; opts : AnimationOptions ; hasChildren : boolean = false ; isPlaying : boolean = false ; hasCompleted : boolean = false ; constructor ( plt , ele ? , opts ? ) { this . plt = plt ; this . element ( ele ) ; this . opts = opts ; } element ( ele ) { if ( ele ) { if ( typeof ele === 'string' ) { ele = this . plt . doc ( ) . querySelectorAll ( ele ) ; for ( let i = 0 ; i < ele . length ; i ++ ) { this . _addEle ( ele [ i ] ) ; } } else if ( ele . length ) { for ( let i = 0 ; i < ele . length ; i ++ ) { this . _addEle ( ele [ i ] ) ; } } else { this . _addEle ( ele ) ; } } return this ; } /**
     * NO DOM
     */ private _addEle ( ele ) { if ( ele . nativeElement ) { ele = ele . nativeElement ; } if ( ( < HTMLElement > ele ) . nodeType === 1 ) { this . _eL = ( this . _e = this . _e || [ ] ) . push ( ele ) ; } } /**
     * Add a child animation to this animation.
     */ add ( childAnimation ) { childAnimation . parent = this ; this . hasChildren = true ; this . _cL = ( this . _c = this . _c || [ ] ) . push ( childAnimation ) ; return this ; } /**
     * Get the duration of this animation. If this animation does
     * not have a duration, then it'll get the duration from its parent.
     */ getDuration ( opts ? ) { if ( opts && isDefined ( opts . duration ) ) { return opts . duration ; } else if ( this . _dur !== null ) { return this . _dur ; } else if ( this . parent ) { return this . parent . getDuration ( ) ; } return 0 ; } /**
     * Returns if the animation is a root one.
     */ isRoot ( ) { return ! this . parent ; } /**
     * Set the duration for this animation.
     */ duration ( milliseconds ) { this . _dur = milliseconds ; return this ; } /**
     * Get the easing of this animation. If this animation does
     * not have an easing, then it'll get the easing from its parent.
     */ getEasing ( ) { if ( this . _rv && this . _rvEs ) { return this . _rvEs ; } return this . _es !== null ? this . _es : ( this . parent && this . parent . getEasing ( ) ) || null ; } /**
     * Set the easing for this animation.
     */ easing ( name ) { this . _es = name ; return this ; } /**
     * Set the easing for this reversed animation.
     */ easingReverse ( name ) { this . _rvEs = name ; return this ; } /**
     * Add the "from" value for a specific property.
     */ from ( prop , val ) { this . _addProp ( 'from' , prop , val ) ; return this ; } /**
     * Add the "to" value for a specific property.
     */ to ( prop , val , clearProperyAfterTransition ? ) { const fx = this . _addProp ( 'to' , prop , val ) ; if ( clearProperyAfterTransition ) { this . afterClearStyles ( [ fx . trans ? this . plt . Css . transform : prop ] ) ; } return this ; } /**
     * Shortcut to add both the "from" and "to" for the same property.
     */ fromTo ( prop , fromVal , toVal , clearProperyAfterTransition ? ) { return this . from ( prop , fromVal ) . to ( prop , toVal , clearProperyAfterTransition ) ; } hidden private _getProp ( name ) { if ( this . _fx ) { return this . _fx . find ( ( prop ) => prop . name === name ) ; } else { this . _fx = [ ] ; } return null ; } private _addProp ( state , prop , val ) { let fxProp = this . _getProp ( prop ) ; if ( ! fxProp ) { const shouldTrans = ( ANIMATION_TRANSFORMS [ prop ] === 1 ) ; fxProp = { name : prop , trans : shouldTrans , wc : ( shouldTrans ? this . plt . Css . transform : prop ) } ; this . _fx . push ( fxProp ) ; } let fxState = { val : val , num : null , unit : '' , } ; fxProp [ state ] = fxState ; if ( typeof val === 'string' && val . indexOf ( ' ' ) < 0 ) { let r = val . match ( ANIMATION_CSS_VALUE_REGEX ) ; let num = parseFloat ( r [ 1 ] ) ; if ( ! isNaN ( num ) ) { fxState . num = num ; } fxState . unit = ( r [ 0 ] !== r [ 2 ] ? r [ 2 ] : '' ) ; } else if ( typeof val === 'number' ) { fxState . num = val ; } return fxProp ; } /**
     * Add CSS class to this animation's elements
     * before the animation begins.
     */ beforeAddClass ( className ) { ( this . _bfAdd = this . _bfAdd || [ ] ) . push ( className ) ; return this ; } /**
     * Remove CSS class from this animation's elements
     * before the animation begins.
     */ beforeRemoveClass ( className ) { ( this . _bfRm = this . _bfRm || [ ] ) . push ( className ) ; return this ; } /**
     * Set CSS inline styles to this animation's elements
     * before the animation begins.
     */ beforeStyles ( styles ) { this . _bfSty = styles ; return this ; } /**
     * Clear CSS inline styles from this animation's elements
     * before the animation begins.
     */ beforeClearStyles ( propertyNames ) { this . _bfSty = this . _bfSty || { } ; for ( let i = 0 ; i < propertyNames . length ; i ++ ) { this . _bfSty [ propertyNames [ i ] ] = '' ; } return this ; } /**
     * Add a function which contains DOM reads, which will run
     * before the animation begins.
     */ beforeAddRead ( domReadFn ) { ( this . _rdFn = this . _rdFn || [ ] ) . push ( domReadFn ) ; return this ; } /**
     * Add a function which contains DOM writes, which will run
     * before the animation begins.
     */ beforeAddWrite ( domWriteFn ) { ( this . _wrFn = this . _wrFn || [ ] ) . push ( domWriteFn ) ; return this ; } /**
     * Add CSS class to this animation's elements
     * after the animation finishes.
     */ afterAddClass ( className ) { ( this . _afAdd = this . _afAdd || [ ] ) . push ( className ) ; return this ; } /**
     * Remove CSS class from this animation's elements
     * after the animation finishes.
     */ afterRemoveClass ( className ) { ( this . _afRm = this . _afRm || [ ] ) . push ( className ) ; return this ; } /**
     * Set CSS inline styles to this animation's elements
     * after the animation finishes.
     */ afterStyles ( styles ) { this . _afSty = styles ; return this ; } /**
     * Clear CSS inline styles from this animation's elements
     * after the animation finishes.
     */ afterClearStyles ( propertyNames ) { this . _afSty = this . _afSty || { } ; for ( let i = 0 ; i < propertyNames . length ; i ++ ) { this . _afSty [ propertyNames [ i ] ] = '' ; } return this ; } /**
     * Play the animation.
     */ play ( opts ? ) { if ( ! this . plt ) { return ; } this . _isAsync = this . _hasDuration ( opts ) ; this . _clearAsync ( ) ; this . _playInit ( opts ) ; this . plt . raf ( ( ) => { this . plt . raf ( this . _playDomInspect . bind ( this , opts ) ) ; } ) ; } syncPlay ( ) { if ( ! this . plt ) { return ; } const opts = { duration : 0 } ; this . _isAsync = false ; this . _clearAsync ( ) ; this . _playInit ( opts ) ; this . _playDomInspect ( opts ) ; } hidden _playInit ( opts ) { this . _twn = false ; this . isPlaying = true ; this . hasCompleted = false ; this . _hasDur = ( this . getDuration ( opts ) > ANIMATION_DURATION_MIN ) ; const children = this . _c ; for ( let i = 0 ; i < this . _cL ; i ++ ) { children [ i ] . _playInit ( opts ) ; } if ( this . _hasDur ) { this . _progress ( 0 ) ; this . _willChg ( true ) ; } } hidden _playDomInspect ( opts ) { this . _beforeAnimation ( ) ; const dur = this . getDuration ( opts ) ; if ( this . _isAsync ) { this . _asyncEnd ( dur , true ) ; } this . _playProgress ( opts ) ; if ( this . _isAsync && this . plt ) { this . plt . raf ( this . _playToStep . bind ( this , 1 ) ) ; } } hidden _playProgress ( opts ) { const children = this . _c ; for ( let i = 0 ; i < this . _cL ; i ++ ) { children [ i ] . _playProgress ( opts ) ; } if ( this . _hasDur ) { this . _setTrans ( this . getDuration ( opts ) , false ) ; } else { this . _progress ( 1 ) ; this . _setAfterStyles ( ) ; this . _didFinish ( true ) ; } } hidden _playToStep ( stepValue ) { const children = this . _c ; for ( let i = 0 ; i < this . _cL ; i ++ ) { children [ i ] . _playToStep ( stepValue ) ; } if ( this . _hasDur ) { this . _progress ( stepValue ) ; } } hidden _asyncEnd ( dur , shouldComplete ) { assert ( ! this . _unrgTrns , '_unrgTrns must be null' ) ; assert ( ! this . _tm , '_tm must be null' ) ; assert ( dur > 0 , 'duration can not be 0 in async animations' ) ; const self = this ; function onTransitionEnd ( ) { self . _clearAsync ( ) ; self . _playEnd ( ) ; self . _didFinishAll ( shouldComplete , true , false ) ; } function onTransitionFallback ( ) { console . debug ( 'Animation onTransitionFallback, CSS onTransitionEnd did not fire!' ) ; self . _tm = undefined ; self . _clearAsync ( ) ; self . _playEnd ( shouldComplete ? 1 : 0 ) ; self . _didFinishAll ( shouldComplete , true , false ) ; } self . _unrgTrns = this . plt . transitionEnd ( self . _transEl ( ) , onTransitionEnd , false ) ; self . _tm = self . plt . timeout ( onTransitionFallback , ( dur + ANIMATION_TRANSITION_END_FALLBACK_PADDING_MS ) ) ; } hidden _playEnd ( stepValue ? ) { const children = this . _c ; for ( let i = 0 ; i < this . _cL ; i ++ ) { children [ i ] . _playEnd ( stepValue ) ; } if ( this . _hasDur ) { if ( isDefined ( stepValue ) ) { this . _setTrans ( 0 , true ) ; this . _progress ( stepValue ) ; } this . _setAfterStyles ( ) ; this . _willChg ( false ) ; } } hidden _hasDuration ( opts ) { if ( this . getDuration ( opts ) > ANIMATION_DURATION_MIN ) { return true ; } const children = this . _c ; for ( let i = 0 ; i < this . _cL ; i ++ ) { if ( children [ i ] . _hasDuration ( opts ) ) { return true ; } } return false ; } hidden _hasDomReads ( ) { if ( this . _rdFn && this . _rdFn . length ) { return true ; } const children = this . _c ; for ( let i = 0 ; i < this . _cL ; i ++ ) { if ( children [ i ] . _hasDomReads ( ) ) { return true ; } } return false ; } /**
     * Immediately stop at the end of the animation.
     */ stop ( stepValue = 1 ) { this . _clearAsync ( ) ; this . _hasDur = true ; this . _playEnd ( stepValue ) ; } hidden _clearAsync ( ) { this . _unrgTrns && this . _unrgTrns ( ) ; this . _tm && clearTimeout ( this . _tm ) ; this . _tm = this . _unrgTrns = undefined ; } hidden _progress ( stepValue ) { let val ; let effects = this . _fx ; let nuElements = this . _eL ; if ( ! effects || ! nuElements ) { return ; } if ( this . _rv ) { stepValue = ( ( stepValue * - 1 ) + 1 ) ; } let i , j ; let finalTransform = '' ; const elements = this . _e ; for ( i = 0 ; i < effects . length ; i ++ ) { const fx = effects [ i ] ; if ( fx . from && fx . to ) { const fromNum = fx . from . num ; const toNum = fx . to . num ; const tweenEffect = ( fromNum !== toNum ) ; assert ( tweenEffect || ! this . _isAsync , 'in async animations to != from value' ) ; if ( tweenEffect ) { this . _twn = true ; } if ( stepValue === 0 ) { val = fx . from . val ; } else if ( stepValue === 1 ) { val = fx . to . val ; } else if ( tweenEffect ) { let valNum = ( ( ( toNum - fromNum ) * stepValue ) + fromNum ) ; const unit = fx . to . unit ; if ( unit === 'px' ) { valNum = Math . round ( valNum ) ; } val = valNum + unit ; } if ( val !== null ) { const prop = fx . name ; if ( fx . trans ) { finalTransform += prop + '(' + val + ') ' ; } else { for ( j = 0 ; j < nuElements ; j ++ ) { ( < any > elements [ j ] . style ) [ prop ] = val ; } } } } } if ( finalTransform . length ) { if ( ! this . _rv && stepValue !== 1 || this . _rv && stepValue !== 0 ) { finalTransform += 'translateZ(0px)' ; } const cssTransform = this . plt . Css . transform ; for ( i = 0 ; i < elements . length ; i ++ ) { ( < any > elements [ i ] . style ) [ cssTransform ] = finalTransform ; } } } hidden _setTrans ( dur , forcedLinearEasing ) { if ( ! this . _fx ) { return ; } const elements = this . _e ; const easing = ( forcedLinearEasing ? 'linear' : this . getEasing ( ) ) ; const durString = dur + 'ms' ; const Css = this . plt . Css ; const cssTransform = Css . transition ; const cssTransitionDuration = Css . transitionDuration ; const cssTransitionTimingFn = Css . transitionTimingFn ; let eleStyle ; for ( let i = 0 ; i < this . _eL ; i ++ ) { eleStyle = elements [ i ] . style ; if ( dur > 0 ) { eleStyle [ cssTransform ] = '' ; eleStyle [ cssTransitionDuration ] = durString ; if ( easing ) { eleStyle [ cssTransitionTimingFn ] = easing ; } } else { eleStyle [ cssTransform ] = 'none' ; } } } hidden _beforeAnimation ( ) { this . _fireBeforeReadFunc ( ) ; this . _fireBeforeWriteFunc ( ) ; this . _setBeforeStyles ( ) ; } hidden _setBeforeStyles ( ) { let i , j ; const children = this . _c ; for ( i = 0 ; i < this . _cL ; i ++ ) { children [ i ] . _setBeforeStyles ( ) ; } if ( this . _rv ) { return ; } const addClasses = this . _bfAdd ; const removeClasses = this . _bfRm ; let ele ; let eleClassList ; let prop ; for ( i = 0 ; i < this . _eL ; i ++ ) { ele = this . _e [ i ] ; eleClassList = ele . classList ; if ( addClasses ) { for ( j = 0 ; j < addClasses . length ; j ++ ) { eleClassList . add ( addClasses [ j ] ) ; } } if ( removeClasses ) { for ( j = 0 ; j < removeClasses . length ; j ++ ) { eleClassList . remove ( removeClasses [ j ] ) ; } } if ( this . _bfSty ) { for ( prop in this . _bfSty ) { ( < any > ele ) . style [ prop ] = this . _bfSty [ prop ] ; } } } } hidden _fireBeforeReadFunc ( ) { const children = this . _c ; for ( let i = 0 ; i < this . _cL ; i ++ ) { children [ i ] . _fireBeforeReadFunc ( ) ; } const readFunctions = this . _rdFn ; if ( readFunctions ) { for ( let i = 0 ; i < readFunctions . length ; i ++ ) { readFunctions [ i ] ( ) ; } } } hidden _fireBeforeWriteFunc ( ) { const children = this . _c ; for ( let i = 0 ; i < this . _cL ; i ++ ) { children [ i ] . _fireBeforeWriteFunc ( ) ; } const writeFunctions = this . _wrFn ; if ( this . _wrFn ) { for ( let i = 0 ; i < writeFunctions . length ; i ++ ) { writeFunctions [ i ] ( ) ; } } } hidden _setAfterStyles ( ) { let i , j ; let ele ; let eleClassList ; let elements = this . _e ; for ( i = 0 ; i < this . _eL ; i ++ ) { ele = elements [ i ] ; eleClassList = ele . classList ; ( < any > ele ) . style [ this . plt . Css . transitionDuration ] = ( < any > ele ) . style [ this . plt . Css . transitionTimingFn ] = '' ; if ( this . _rv ) { if ( this . _bfAdd ) { for ( j = 0 ; j < this . _bfAdd . length ; j ++ ) { eleClassList . remove ( this . _bfAdd [ j ] ) ; } } if ( this . _bfRm ) { for ( j = 0 ; j < this . _bfRm . length ; j ++ ) { eleClassList . add ( this . _bfRm [ j ] ) ; } } if ( this . _bfSty ) { for ( const prop in this . _bfSty ) { ( < any > ele ) . style [ prop ] = '' ; } } } else { if ( this . _afAdd ) { for ( j = 0 ; j < this . _afAdd . length ; j ++ ) { eleClassList . add ( this . _afAdd [ j ] ) ; } } if ( this . _afRm ) { for ( j = 0 ; j < this . _afRm . length ; j ++ ) { eleClassList . remove ( this . _afRm [ j ] ) ; } } if ( this . _afSty ) { for ( const prop in this . _afSty ) { ( < any > ele ) . style [ prop ] = this . _afSty [ prop ] ; } } } } } hidden _willChg ( addWillChange ) { let wc ; let effects = this . _fx ; let willChange ; if ( addWillChange && effects ) { wc = [ ] ; for ( let i = 0 ; i < effects . length ; i ++ ) { const propWC = effects [ i ] . wc ; if ( propWC === 'webkitTransform' ) { wc . push ( 'transform' , '-webkit-transform' ) ; } else { wc . push ( propWC ) ; } } willChange = wc . join ( ',' ) ; } else { willChange = '' ; } for ( let i = 0 ; i < this . _eL ; i ++ ) { ( < any > this . _e [ i ] ) . style . willChange = willChange ; } } /**
     * Start the animation with a user controlled progress.
     */ progressStart ( ) { this . _clearAsync ( ) ; this . _beforeAnimation ( ) ; this . _progressStart ( ) ; } hidden _progressStart ( ) { const children = this . _c ; for ( let i = 0 ; i < this . _cL ; i ++ ) { children [ i ] . _progressStart ( ) ; } this . _setTrans ( 0 , true ) ; this . _willChg ( true ) ; } /**
     * Set the progress step for this animation.
     * progressStep() is not debounced, so it should not be called faster than 60FPS.
     */ progressStep ( stepValue ) { stepValue = Math . min ( 1 , Math . max ( 0 , stepValue ) ) ; const children = this . _c ; for ( let i = 0 ; i < this . _cL ; i ++ ) { children [ i ] . progressStep ( stepValue ) ; } if ( this . _rv ) { stepValue = ( ( stepValue * - 1 ) + 1 ) ; } this . _progress ( stepValue ) ; } /**
     * End the progress animation.
     */ progressEnd ( shouldComplete , currentStepValue , dur = - 1 ) { console . debug ( 'Animation, progressEnd, shouldComplete' , shouldComplete , 'currentStepValue' , currentStepValue ) ; if ( this . _rv ) { currentStepValue = ( ( currentStepValue * - 1 ) + 1 ) ; } const stepValue = shouldComplete ? 1 : 0 ; const diff = Math . abs ( currentStepValue - stepValue ) ; if ( diff < 0.05 ) { dur = 0 ; } else if ( dur < 0 ) { dur = this . _dur ; } this . _isAsync = ( dur > 30 ) ; this . _progressEnd ( shouldComplete , stepValue , dur , this . _isAsync ) ; if ( this . _isAsync ) { this . _asyncEnd ( dur , shouldComplete ) ; this . plt && this . plt . raf ( this . _playToStep . bind ( this , stepValue ) ) ; } } hidden _progressEnd ( shouldComplete , stepValue , dur , isAsync ) { const children = this . _c ; for ( let i = 0 ; i < this . _cL ; i ++ ) { children [ i ] . _progressEnd ( shouldComplete , stepValue , dur , isAsync ) ; } if ( ! isAsync ) { this . _progress ( stepValue ) ; this . _willChg ( false ) ; this . _setAfterStyles ( ) ; this . _didFinish ( shouldComplete ) ; } else { this . isPlaying = true ; this . hasCompleted = false ; this . _hasDur = true ; this . _willChg ( true ) ; this . _setTrans ( dur , false ) ; } } /**
     * Add a callback to fire when the animation has finished.
     */ onFinish ( callback , onceTimeCallback = false , clearOnFinishCallacks = false ) { if ( clearOnFinishCallacks ) { this . _fFn = this . _fOneFn = undefined ; } if ( onceTimeCallback ) { this . _fOneFn = this . _fOneFn || [ ] ; this . _fOneFn . push ( callback ) ; } else { this . _fFn = this . _fFn || [ ] ; this . _fFn . push ( callback ) ; } return this ; } hidden _didFinishAll ( hasCompleted , finishAsyncAnimations , finishNoDurationAnimations ) { const children = this . _c ; for ( let i = 0 ; i < this . _cL ; i ++ ) { children [ i ] . _didFinishAll ( hasCompleted , finishAsyncAnimations , finishNoDurationAnimations ) ; } if ( finishAsyncAnimations && this . _isAsync || finishNoDurationAnimations && ! this . _isAsync ) { this . _didFinish ( hasCompleted ) ; } } hidden _didFinish ( hasCompleted ) { this . isPlaying = false ; this . hasCompleted = hasCompleted ; if ( this . _fFn ) { for ( let i = 0 ; i < this . _fFn . length ; i ++ ) { this . _fFn [ i ] ( this ) ; } } if ( this . _fOneFn ) { for ( let i = 0 ; i < this . _fOneFn . length ; i ++ ) { this . _fOneFn [ i ] ( this ) ; } this . _fOneFn . length = 0 ; } } /**
     * Reverse the animation.
     */ reverse ( shouldReverse = true ) { const children = this . _c ; for ( let i = 0 ; i < this . _cL ; i ++ ) { children [ i ] . reverse ( shouldReverse ) ; } this . _rv = shouldReverse ; return this ; } /**
     * Recursively destroy this animation and all child animations.
     */ destroy ( ) { const children = this . _c ; for ( let i = 0 ; i < this . _cL ; i ++ ) { children [ i ] . destroy ( ) ; } this . _clearAsync ( ) ; this . parent = this . plt = this . _e = this . _rdFn = this . _wrFn = null ; if ( this . _c ) { this . _c . length = this . _cL = 0 ; } if ( this . _fFn ) { this . _fFn . length = 0 ; } if ( this . _fOneFn ) { this . _fOneFn . length = 0 ; } } hidden _transEl ( ) { let targetEl ; for ( let i = 0 ; i < this . _cL ; i ++ ) { targetEl = this . _c [ i ] . _transEl ( ) ; if ( targetEl ) { return targetEl ; } } return ( this . _twn && this . _hasDur && this . _eL ? this . _e [ 0 ] : null ) ; } } export interface AnimationOptions { animation ? : string ; duration ? : number ; easing ? : string ; direction ? : string ; isRTL ? : boolean ; ev ? : any ; } export interface PlayOptions { duration ? : number ; } export interface EffectProperty { name : string ; trans : boolean ; wc ? : string ; to ? : EffectState ; from ? : EffectState ; } export interface EffectState { val : any ; num : number ; unit : string ; } const ANIMATION_TRANSFORMS = { 'translateX' : 1 , 'translateY' : 1 , 'translateZ' : 1 , 'scale' : 1 , 'scaleX' : 1 , 'scaleY' : 1 , 'scaleZ' : 1 , 'rotate' : 1 , 'rotateX' : 1 , 'rotateY' : 1 , 'rotateZ' : 1 , 'skewX' : 1 , 'skewY' : 1 , 'perspective' : 1 } ; const ANIMATION_CSS_VALUE_REGEX = /(^-?\d*\.?\d*)(.*)/ ; const ANIMATION_DURATION_MIN = 32 ; const ANIMATION_TRANSITION_END_FALLBACK_PADDING_MS = 400 ;