import { Directive , ElementRef , Input , HostListener } from '@angular/core' ; @ Directive ( { selector : '[scrollTo]' } ) export class ScrollTo { @ Input ( ) scrollableElementSelector : string ; @ Input ( ) scrollTargetSelector : string ; @ Input ( ) scrollYTarget : number ; @ Input ( ) scrollOffSet : number ; constructor ( private el ) { } @ HostListener ( 'click' , [ '$event' ] ) onClick ( event ) { event . preventDefault ( ) ; let scrollEnd ; if ( this . scrollYTarget ) { if ( isNaN ( Number ( this . scrollYTarget ) ) ) { throw 'scrollYTarget must have numerical values' ; } scrollEnd = this . scrollYTarget ; } let target ; if ( scrollEnd == null ) { target = this . getTarget ( ) ; if ( ! target ) { console . warn ( 'target element do not exist' ) ; return ; } scrollEnd = target . offsetTop ; } let scrollingElement = this . getScrollableElement ( target ) ; try { if ( scrollingElement === document . body ) { this . smoothScroll ( document . documentElement , scrollEnd ) ; } } catch ( e ) { console . warn ( e ) ; } this . smoothScroll ( scrollingElement , scrollEnd ) ; } private getScrollableElement ( target ) { let scrollableElement ; if ( this . scrollableElementSelector ) { scrollableElement = < HTMLElement > document . querySelector ( this . scrollableElementSelector ) ; } else if ( target != null ) { scrollableElement = this . findScrollableParent ( target ) ; } else { scrollableElement = this . findMainScrollableElement ( ) ; } return scrollableElement ; } private getTarget ( ) { let target ; if ( this . scrollTargetSelector ) { target = < HTMLElement > document . querySelector ( this . scrollTargetSelector ) ; } else if ( this . el . nativeElement . href ) { let href = '#' + this . el . nativeElement . href . split ( '#' ) [ 1 ] ; target = < HTMLElement > document . querySelector ( href ) ; } return target ; } private smoothScroll ( element , end ) { const duration = 500 ; const clock = Date . now ( ) ; const requestAnimationFrame = window . requestAnimationFrame || function ( fn ) { window . setTimeout ( fn , 15 ) ; } ; let offset = 0 ; if ( this . scrollOffSet ) { if ( isNaN ( Number ( this . scrollOffSet ) ) ) { throw 'scrollOffSet must have numerical values' ; } end -= this . scrollOffSet ; } const start = element . scrollTop ; let step = ( ) => { let elapsed = Date . now ( ) - clock ; let position = this . position ( start , end , elapsed , duration ) ; element . scrollTop = position ; if ( elapsed > duration ) { } else { requestAnimationFrame ( step ) ; } } ; step ( ) ; } easeInOutCubic ( t ) { return t < .5 ? 4 * t * t * t : ( t - 1 ) * ( 2 * t - 2 ) * ( 2 * t - 2 ) + 1 ; } /**
      * calculate the scroll position we should be in
      * given the start and end point of the scroll
      * the time elapsed from the beginning of the scroll
      * and the total duration of the scroll (default 500ms)
      */ private position ( start , end , elapsed , duration ) { if ( elapsed > duration ) { return end ; } ; return start + ( end - start ) * this . easeInOutCubic ( elapsed / duration ) ; } method param { HTMLElement } element returns { HTMLElement } private findScrollableParent ( element ) { let isBody , hasScrollableSpace , hasVisibleOverflow ; do { element = element . parentElement ; isBody = element === document . body ; hasScrollableSpace = element . clientHeight < element . scrollHeight ; hasVisibleOverflow = getComputedStyle ( element , null ) . overflow === 'visible' ; } while ( ! isBody && ! ( hasScrollableSpace && ! hasVisibleOverflow ) ) ; return element ; } method returns { HTMLElement } private findMainScrollableElement ( ) { let element = this . findScrollableChild ( document . body ) ; if ( element != null ) { return element ; } return document . body ; } private isScrollable ( element ) { let hasScrollableSpace = element . clientHeight < element . scrollHeight ; let hasVisibleOverflow = getComputedStyle ( element , null ) . overflow === 'visible' ; return hasScrollableSpace && ! hasVisibleOverflow ; } private isScriptTag ( element ) { return element . nodeName === 'SCRIPT' ; } private findScrollableChild ( inputElement ) { let scrollableElement ; let i = 0 ; if ( this . isScriptTag ( inputElement ) ) { return null ; } while ( scrollableElement == null && i < inputElement . childElementCount ) { let element = < HTMLElement > inputElement . children [ i ] ; if ( this . isScrollable ( element ) ) { scrollableElement = element ; return element ; } scrollableElement = this . findScrollableChild ( element ) ; i ++ ; } return scrollableElement ; } }