Angular 4 - how to trigger animation when a div appears in the viewport?

I am creating a new site using Angular 4 and I am trying to restore the effect when when the div becomes visible (when scrolling down the screen) then it can trigger an Angular animation to displace the div in the shape of the sides.

I've been able to do this in the past using jQuery outside of Angular 4, but I want to try and create the same effect using native Angular 4 animations.

Can anyone offer me some advice on how to trigger the animation when the div appears in view (i.e. scrolls down to the bottom of the page as it enters the viewport?). I've already written a slide animation, but I don't know how to trigger this by scrolling when the div becomes visible later on to the view port.

Thanks everyone!

+7


source to share


3 answers


I created a directive that fires an event as soon as an element is either fully in view or its top edge has reached the top edge of the view.

Here is the plunker: https://embed.plnkr.co/mlez1dXjR87FNBHXq1YM/

Used like this:



 <div (appear)="onAppear()">...</div>

      

Here is the directive:

import {
  ElementRef, Output, Directive, AfterViewInit, OnDestroy, EventEmitter
} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {Subscription} from 'rxjs/Subscription';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/startWith';

@Directive({
  selector: '[appear]'
})
export class AppearDirective implements AfterViewInit, OnDestroy {
  @Output()
  appear: EventEmitter<void>;

  elementPos: number;
  elementHeight: number;

  scrollPos: number;
  windowHeight: number;

  subscriptionScroll: Subscription;
  subscriptionResize: Subscription;

  constructor(private element: ElementRef){
    this.appear = new EventEmitter<void>();
  }

  saveDimensions() {
    this.elementPos = this.getOffsetTop(this.element.nativeElement);
    this.elementHeight = this.element.nativeElement.offsetHeight;
    this.windowHeight = window.innerHeight;
  }
  saveScrollPos() {
    this.scrollPos = window.scrollY;
  }
  getOffsetTop(element: any){
    let offsetTop = element.offsetTop || 0;
    if(element.offsetParent){
      offsetTop += this.getOffsetTop(element.offsetParent);
    }
    return offsetTop;
  }
  checkVisibility(){
    if(this.isVisible()){
      // double check dimensions (due to async loaded contents, e.g. images)
      this.saveDimensions();
      if(this.isVisible()){
        this.unsubscribe();
        this.appear.emit();
      }
    }
  }
  isVisible(){
    return this.scrollPos >= this.elementPos || (this.scrollPos + this.windowHeight) >= (this.elementPos + this.elementHeight);
  }

  subscribe(){
    this.subscriptionScroll = Observable.fromEvent(window, 'scroll').startWith(null)
      .subscribe(() => {
        this.saveScrollPos();
        this.checkVisibility();
      });
    this.subscriptionResize = Observable.fromEvent(window, 'resize').startWith(null)
      .subscribe(() => {
        this.saveDimensions();
        this.checkVisibility();
      });
  }
  unsubscribe(){
    if(this.subscriptionScroll){
      this.subscriptionScroll.unsubscribe();
    }
    if(this.subscriptionResize){
      this.subscriptionResize.unsubscribe();
    }
  }

  ngAfterViewInit(){
    this.subscribe();
  }
  ngOnDestroy(){
    this.unsubscribe();
  }
}

      

+9


source


I created a base component that exposes a appearedOnce flag that returns once if the component is either fully visible or the top edge has a top edge.

@Injectable()
export class AppearOnce implements AfterViewInit, OnDestroy {
  appearedOnce: boolean;

  elementPos: number;
  elementHeight: number;

  scrollPos: number;
  windowHeight: number;

  subscriptionScroll: Subscription;
  subscriptionResize: Subscription;

  constructor(private element: ElementRef, private cdRef: ChangeDetectorRef){}
  onResize() {
    this.elementPos = this.getOffsetTop(this.element.nativeElement);
    this.elementHeight = this.element.nativeElement.clientHeight;
    this.checkVisibility();
  }
  onScroll() {
    this.scrollPos = window.scrollY;
    this.windowHeight = window.innerHeight;
    this.checkVisibility();
  }
  getOffsetTop(element: any){
    let offsetTop = element.offsetTop || 0;
    if(element.offsetParent){
      offsetTop += this.getOffsetTop(element.offsetParent);
    }
    return offsetTop;
  }

  checkVisibility(){
    if(!this.appearedOnce){
      if(this.scrollPos >= this.elementPos || (this.scrollPos + this.windowHeight) >= (this.elementPos + this.elementHeight)){
        this.appearedOnce = true;
        this.unsubscribe();
        this.cdRef.detectChanges();
      }
    }
  }

  subscribe(){
    this.subscriptionScroll = Observable.fromEvent(window, 'scroll').startWith(null)
      .subscribe(() => this.onScroll());
    this.subscriptionResize = Observable.fromEvent(window, 'resize').startWith(null)
      .subscribe(() => this.onResize());
  }
  unsubscribe(){
    if(this.subscriptionScroll){
      this.subscriptionScroll.unsubscribe();
    }
    if(this.subscriptionResize){
      this.subscriptionResize.unsubscribe();
    }
  }

  ngAfterViewInit(){
    this.subscribe();
  }
  ngOnDestroy(){
    this.unsubscribe();
  }
}

      

You can simply extend this component and use the default inheritance property

@Component({
  template: `
    <div>
      <div *ngIf="appearedOnce">...</div>
      ...
    </div>
  `
})
class MyComponent extends AppearOnceComponent {
    ...
}

      



Be aware of the super () call if you need to overwrite the constructor or lifecyclehooks.

(edit) plunker : https://embed.plnkr.co/yIpA1mI1b9kVoEXGy6Hh/

(edit) I turned this into a directive in another answer below.

+2


source


Simple way if you want it in a specific component:

@ViewChild('chatTeaser') chatTeaser: ElementRef;

@HostListener('window:scroll')
checkScroll() {
    const scrollPosition = window.pageYOffset + window.innerHeight;

    if (this.chatTeaser && this.chatTeaser.nativeElement.offsetTop >= scrollPosition) {
        this.animateAvatars();
    }
}

      

And in HTML:

<div id="chat-teaser" #chatTeaser>

      

It is when the top of the element is scrolled to the function is called. If you only want to call the function when the full div is displayed, add the height of the div to this.chatTeaser.nativeElement.offsetTop

.

0


source







All Articles