import { Injectable , ElementRef , EventEmitter , Renderer2 , Inject , PLATFORM_ID , RendererFactory2 } from '@angular/core' ; import { isPlatformBrowser } from '@angular/common' ; class State { aw : number = 0 ; ah : number = 0 ; w : number = 0 ; h : number = 0 ; get ar ( ) : number { return this . w / this . h ; } get diag ( ) : number { return Math . sqrt ( ( this . w * this . w ) + ( this . h * this . h ) ) ; } get valid ( ) : boolean { return this . w > 0 && this . h > 0 && this . aw > 0 && this . ah > 0 ; } get widthBound ( ) : boolean { return this . ar > ( this . aw / this . ah ) ; } } @ Injectable ( ) export class PointerService { private _disableSwiping = false ; private _enableZoom = false ; private _enablePan = false ; private _startEVCache : PointerEvent = null ; private _evCache : Array < PointerEvent > = new Array ( ) ; private _previousDiagonal : number = - 1 ; private _originalState : State = new State ( ) ; private _slideEvent : EventEmitter < number > = new EventEmitter < number > ( true ) ; private _clickEvent : EventEmitter < void > = new EventEmitter < void > ( true ) ; private pointerUp = ( event ) => { this . _pointerUp ( event ) ; } ; private pointerDown = ( event ) => { this . _pointerDown ( event ) ; } ; private pointerMove = ( event ) => { this . _pointerMove ( event ) ; } ; public set disableSwiping ( state ) { this . _disableSwiping = state ; } public set enableZoom ( state ) { this . _enableZoom = state ; } public set enablePan ( state ) { this . _enablePan = state ; } bind ( el ) { if ( isPlatformBrowser ( this . platform_id ) ) { el . nativeElement . addEventListener ( 'pointerdown' , this . pointerDown ) ; el . nativeElement . addEventListener ( 'pointerup' , this . pointerUp ) ; el . nativeElement . addEventListener ( 'pointercancel' , this . pointerUp ) ; el . nativeElement . addEventListener ( 'pointerout' , this . pointerUp ) ; el . nativeElement . addEventListener ( 'pointerleave' , this . pointerUp ) ; el . nativeElement . addEventListener ( 'pointermove' , this . pointerMove ) ; } } unbind ( el ) { if ( isPlatformBrowser ( this . platform_id ) ) { el . nativeElement . removeEventListener ( 'pointerdown' , this . pointerDown ) ; el . nativeElement . removeEventListener ( 'pointerup' , this . pointerUp ) ; el . nativeElement . removeEventListener ( 'pointercancel' , this . pointerUp ) ; el . nativeElement . removeEventListener ( 'pointerout' , this . pointerUp ) ; el . nativeElement . removeEventListener ( 'pointerleave' , this . pointerUp ) ; el . nativeElement . removeEventListener ( 'pointermove' , this . pointerMove ) ; } } public get slideEvent ( ) : EventEmitter < Number > { return this . _slideEvent ; } public get clickEvent ( ) : EventEmitter < void > { return this . _clickEvent ; } constructor ( @ Inject ( PLATFORM_ID ) private platform_id ) { } private _pointerDown ( e ) { this . _evCache . push ( e ) ; if ( this . _evCache . length === 1 ) { this . _startEVCache = e ; if ( this . _enablePan || this . _enableZoom ) { this . _loadOriginalState ( e ) ; this . _convertBGSizeToPixels ( e ) ; this . _convertBGPosToPixels ( e ) ; } } } private _loadOriginalState ( e ) { if ( ! this . _originalState . valid && e . target && ( e . target as any ) . style && ( e . target as any ) . style . backgroundImage ) { const imgUrlArr = ( e . target as any ) . style . backgroundImage . match ( /^url\(["']?(.+?)["']?\)$/ ) ; const img = new Image ( ) ; img . src = imgUrlArr [ 1 ] ; this . _originalState . aw = ( e . target as any ) . offsetWidth ; this . _originalState . ah = ( e . target as any ) . offsetHeight ; this . _originalState . w = img . width ; this . _originalState . h = img . height ; } } private _convertBGSizeToPixels ( e ) { const imgElement = ( e . target as any ) ; let bgSize = imgElement . style . backgroundSize ; if ( bgSize . indexOf ( ' ' ) > - 1 ) { const sizeTuple = bgSize . split ( ' ' ) ; bgSize = this . _originalState . widthBound ? sizeTuple [ 0 ] : sizeTuple [ 1 ] ; } if ( bgSize === 'cover' ) { bgSize = this . _originalState . widthBound ? this . _originalState . ah * this . _originalState . ar : this . _originalState . aw ; } else if ( bgSize . indexOf ( 'px' ) > - 1 ) { bgSize = bgSize . substring ( 0 , bgSize . length - 2 ) ; } else if ( bgSize . indexOf ( '%' ) > - 1 ) { const bgSizePercentage = Number ( bgSize . substring ( 0 , bgSize . length - 1 ) ) / 100 ; bgSize = this . _originalState . widthBound ? this . _originalState . aw * bgSizePercentage : this . _originalState . ah * bgSizePercentage * this . _originalState . ar ; } else if ( bgSize === 'auto' ) { bgSize = this . _originalState . w ; } else if ( bgSize === 'contain' ) { bgSize = this . _originalState . widthBound ? this . _originalState . aw : this . _originalState . ah * this . _originalState . ar ; } else { bgSize = this . _originalState . widthBound ? this . _originalState . aw : this . _originalState . ah * this . _originalState . ar ; } imgElement . style . backgroundSize = bgSize + 'px auto' ; } private _convertBGPosToPixels ( e ) { if ( this . _originalState . valid ) { const imgElement = ( e . target as any ) ; const bgSize = this . _currentBGSize ( e ) ; let bgPosX = imgElement . style . backgroundPositionX ; if ( bgPosX . indexOf ( 'px' ) === - 1 ) { bgPosX = this . _convertLiteralPosToPercentage ( bgPosX ) ; if ( bgPosX . indexOf ( '%' ) > - 1 ) { let bgPosXPercentage = Number ( bgPosX . substring ( 0 , bgPosX . length - 1 ) ) / 100 ; bgPosX = bgPosXPercentage * ( this . _originalState . aw - bgSize ) ; } imgElement . style . backgroundPositionX = bgPosX + 'px' ; } let bgPosY = imgElement . style . backgroundPositionY ; if ( bgPosY . indexOf ( 'px' ) === - 1 ) { bgPosY = this . _convertLiteralPosToPercentage ( bgPosY ) ; if ( bgPosY . indexOf ( '%' ) > - 1 ) { let bgPosYPercentage = Number ( bgPosY . substring ( 0 , bgPosY . length - 1 ) ) / 100 ; bgPosY = bgPosYPercentage * ( this . _originalState . ah - ( bgSize / this . _originalState . ar ) ) ; } imgElement . style . backgroundPositionY = bgPosY + 'px' ; } } } private _pointerUp ( e ) { for ( let i = 0 ; i < this . _evCache . length ; i ++ ) { if ( this . _evCache [ i ] . pointerId === e . pointerId ) { this . _evCache . splice ( i , 1 ) ; break ; } } if ( this . _evCache . length !== 2 ) { this . _previousDiagonal = - 1 ; } if ( this . _evCache . length === 0 && this . _startEVCache !== null ) { this . _checkClickOrSwipe ( e ) ; this . _startEVCache = null ; this . _originalState = new State ( ) ; } } /**
     * 0th Check
     * target is a slide
     *
     * 1st Check for click
     * or x movement is less than 15 px and y movement is less than 15 px
     *
     * 2nd if not click, check for swipe
     * duration < 1000 ms
     * and x movement is >= 30 px
     * and y movement is <= 100 px
     */ private _checkClickOrSwipe ( e ) { if ( ! this . _targetIsASlide ( e ) ) { return ; } const duration = e . timeStamp - this . _startEVCache . timeStamp ; const dirX = e . pageX - this . _startEVCache . pageX ; const dirY = e . pageY - this . _startEVCache . pageY ; if ( ! this . _enablePan && Math . abs ( dirX ) < 15 && Math . abs ( dirY ) < 15 ) { this . _clickEvent . emit ( ) ; } else if ( duration < 1000 && Math . abs ( dirY ) <= 100 && Math . abs ( dirX ) >= 30 && ! this . _disableSwiping && this . _cannotPanMoreTest ( e , dirX ) ) { this . _slideEvent . emit ( dirX < 0 ? 1 : - 1 ) ; } } private _targetIsASlide ( e ) { return ( e . target as any ) . classList . contains ( 'slides' ) ; } private _cannotPanMoreTest ( e , dirX ) { if ( ! this . _enablePan ) { return true ; } const xPos = this . _currentBGPosX ( e ) ; const bgSize = this . _currentBGSize ( e ) ; if ( dirX < 0 && bgSize > this . _originalState . aw && Math . round ( this . _originalState . aw - bgSize - xPos ) < 0 ) { return false ; } else if ( dirX > 0 && bgSize > this . _originalState . aw && xPos < 0 ) { return false ; } return true ; } private _pointerMove ( e ) { e . preventDefault ( ) ; if ( this . _evCache . length === 1 && this . _enablePan ) { this . _1pointMoveAction ( e ) ; } for ( let i = 0 ; i < this . _evCache . length ; i ++ ) { if ( e . pointerId === this . _evCache [ i ] . pointerId ) { this . _evCache [ i ] = e ; break ; } } if ( this . _evCache . length === 2 && this . _enableZoom ) { this . _2pointMoveAction ( e ) ; } } private _1pointMoveAction ( e ) { if ( this . _evCache [ 0 ] . pointerId === e . pointerId ) { const dx = this . _evCache [ 0 ] . pageX - e . pageX ; const dy = this . _evCache [ 0 ] . pageY - e . pageY ; if ( this . _originalState . valid && ( dx !== 0 || dy !== 0 ) ) { this . _transformBGPosition ( e , dx , dy ) ; } } } private _2pointMoveAction ( e ) { const x = Math . abs ( this . _evCache [ 0 ] . pageX - this . _evCache [ 1 ] . pageX ) ; const y = Math . abs ( this . _evCache [ 0 ] . pageY - this . _evCache [ 1 ] . pageY ) ; let currentDiagonal = Math . sqrt ( ( x * x ) + ( y * y ) ) ; if ( this . _previousDiagonal > 0 && this . _originalState . valid ) { const deltaX = currentDiagonal - this . _previousDiagonal ; this . _transformBGSize ( e , deltaX ) ; } this . _previousDiagonal = currentDiagonal ; } private _transformBGPosition ( e , dx , dy ) { const imgElement = ( e . target as any ) ; const previousPosX = this . _currentBGPosX ( e ) ; const previousPosY = this . _currentBGPosY ( e ) ; const newPosX = this . _newBGPosXConstraint ( previousPosX - dx , e ) ; const newPosY = this . _newBGPosYConstraint ( previousPosY - dy , e ) ; if ( newPosX !== previousPosX || newPosY !== previousPosY ) { this . _setBGPos ( imgElement , newPosX , newPosY ) ; } } private _setBGPos ( element , x , y ) { element . style . backgroundPositionX = x + 'px' ; element . style . backgroundPositionY = y + 'px' ; } private _currentBGPosX ( e ) { let bgPosX = ( e . target as any ) . style . backgroundPositionX ; if ( bgPosX . indexOf ( 'px' ) > - 1 ) { bgPosX = bgPosX . substring ( 0 , bgPosX . length - 2 ) ; } return Number ( bgPosX ) ; } private _currentBGPosY ( e ) { let bgPosY = ( e . target as any ) . style . backgroundPositionY ; if ( bgPosY . indexOf ( 'px' ) > - 1 ) { bgPosY = bgPosY . substring ( 0 , bgPosY . length - 2 ) ; } return Number ( bgPosY ) ; } private _convertLiteralPosToPercentage ( literal ) { if ( literal === 'center' ) { return '50%' ; } else if ( literal === 'top' || literal === 'left' ) { return '0%' ; } else if ( literal === 'bottom' || literal === 'right' ) { return '100%' ; } } private _transformBGSize ( e , deltaX ) { const imgElement = ( e . target as any ) ; const currentSize = this . _currentBGSize ( e ) ; const newSize = this . _newBGSizeConstraint ( currentSize + deltaX ) ; if ( newSize !== currentSize ) { this . _setBGSize ( imgElement , newSize ) ; } } private _setBGSize ( element , size ) { element . style . backgroundSize = size + 'px auto' ; element . style . touchAction = 'none' ; } private _currentBGSize ( e ) { const bgSize = ( e . target as any ) . style . backgroundSize ; if ( bgSize . indexOf ( ' ' ) > - 1 ) { const sizeTuple = bgSize . split ( ' ' ) ; const size = this . _originalState . widthBound ? sizeTuple [ 0 ] . substring ( 0 , sizeTuple [ 0 ] . length - 2 ) : sizeTuple [ 1 ] . substring ( 0 , sizeTuple [ 1 ] . length - 2 ) ; return Number ( size ) ; } else if ( bgSize . indexOf ( 'px' ) > - 1 ) { const size = bgSize . substring ( 0 , bgSize . length - 2 ) ; return Number ( size ) ; } } private _newBGSizeConstraint ( newSize ) { if ( this . _originalState . widthBound ) { return newSize < this . _originalState . aw ? this . _originalState . aw : newSize ; } else { return newSize / this . _originalState . ar < this . _originalState . ah ? this . _originalState . ah * this . _originalState . ar : newSize ; } } private _newBGPosXConstraint ( newX , e ) { const bgSize = this . _currentBGSize ( e ) ; if ( bgSize >= this . _originalState . aw ) { if ( newX > 0 ) { return 0 ; } else if ( newX < this . _originalState . aw - bgSize ) { return this . _originalState . aw - bgSize ; } } else { if ( newX < 0 ) { return 0 ; } else if ( newX > this . _originalState . aw - bgSize ) { return this . _originalState . aw - bgSize ; } } return newX ; } private _newBGPosYConstraint ( newY , e ) { const bgSize = this . _currentBGSize ( e ) ; if ( bgSize / this . _originalState . ar >= this . _originalState . ah ) { if ( newY > 0 ) { return 0 ; } else if ( newY < this . _originalState . ah - ( bgSize / this . _originalState . ar ) ) { return this . _originalState . ah - ( bgSize / this . _originalState . ar ) ; } } else { if ( newY < 0 ) { return 0 ; } else if ( newY > this . _originalState . ah - ( bgSize / this . _originalState . ar ) ) { return this . _originalState . ah - ( bgSize / this . _originalState . ar ) ; } } return newY ; } }