JonnyBGod / ngx-scrollspy

Angular ScrollSpy Service

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

scrollspy-style plugin for animations

xmlking opened this issue · comments

I like to submit scrollspy-style plugin that adds a css class if the element is in viewport.
It can be used for adding scroll animations to elements or hide/show navbar.

import {
  Directive, EventEmitter, Output, OnDestroy, ElementRef, Input, AfterViewInit, Inject,
  Renderer, OnInit
} from '@angular/core';

import {Subscription} from 'rxjs/Subscription';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/debounceTime';
import {ScrollSpyService} from 'ng2-scrollspy';
import {WindowService} from '../../../core/services/window.service';
import {DOCUMENT} from '@angular/platform-browser';

declare var $: any;


export interface ScrollSpyStyleOptions {
  className?: string;
  debounce?: number;
  cushion?: number;
  fullyInView?: boolean;
}
@Directive({
  selector: '[scrollSpyStyle]',
})
export class ScrollSpyStyleDirective implements OnInit, OnDestroy, AfterViewInit {

  private sub: Subscription;
  private element: HTMLElement;

  @Input('scrollSpyStyle') public options: ScrollSpyStyleOptions;
  @Output() onVisibilityChange: EventEmitter<boolean> = new EventEmitter();

  private defaultOptions: ScrollSpyStyleOptions = {
    className: 'appeared',
    debounce: 0,
    cushion: 0,
    fullyInView: false
  };

  constructor(private scrollSpyService: ScrollSpyService, elementRef: ElementRef,
              @Inject(DOCUMENT) private _document: Document, private renderer: Renderer,
              @Inject(WindowService) private _window: Window) {
    this.element = elementRef.nativeElement;
  }

  ngOnInit() {
    if (!this.options) {
      this.options = this.defaultOptions;
    } else {
      this.options = Object.assign(this.defaultOptions, this.options);
    }
  }

  ngAfterViewInit() {
    this.sub = this.scrollSpyService.getObservable('window')
      // .debounce(this.options.debounce)
      .map(() => this.isElementInViewport())
      .distinctUntilChanged()
      .subscribe((inView: boolean) => {
        console.log('inView', inView);
        this.onVisibilityChange.emit(inView);
        this.renderer.setElementClass(this.element, this.options.className, inView);
      });
  }

  ngOnDestroy() {
    this.sub.unsubscribe();
  }

  private isElementInViewport() {
    let rect = this.element.getBoundingClientRect();
    const html = this._document.documentElement;

    if (this.options.cushion !== 0) {
      rect = this.addCushion(rect, this.options.cushion);
    }
    if (this.options.fullyInView === true) {
      return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (this._window.innerHeight || html.clientHeight) &&
        rect.right <= (this._window.innerWidth || html.clientWidth)
      );
    } else {
      return (
        rect.bottom >= 0 &&
        rect.right >= 0 &&
        rect.top <= (this._window.innerHeight || html.clientHeight) &&
        rect.left <= (this._window.innerWidth || html.clientWidth)
      );
    }
  }

  /**
   *  If a cushion is specified, the properties are adjusted according to the cushion amount.
   *  If the cushion is positive the rectangle will represent an area that is larger that the actual element.
   *  If the cushion is negative then the rectangle will represent an area that is smaller that the actual element.
   */
  private addCushion(rect: ClientRect, cushion: number) {
    return {
      right: rect.right + cushion,
      left: rect.left - cushion,
      top: rect.top - cushion,
      bottom: rect.bottom + cushion,
      get width() {return this.right - this.left; },
      get height() {return this.bottom - this.top; },
    };
  }
}

Cool thanks.

Could you provide a PR for this plugin?