Angular 4-当Div进入视口时,如何触发动画



我一直在使用Angular 4构建一个新站点,并且我正在尝试重新创建效果,而当DIV变得可见时(当您向下滚动屏幕时(,那么可以然后触发一个角动画,以形式在侧面滑动。

过去,我能够使用Angular 4之外的jQuery做到这一点,但是我想尝试使用本机Angular 4动画创建相同的效果。

任何人都可以为我提供有关如何触发动画时的建议(即,在进入视口时滚动到页面下部?(。我已经写了幻灯片动画,但是当Div在以后到达视图端口时,我不知道如何用滚动触发它。

谢谢大家!

我创建了一个指令,该指令在元素完全在视图中或其上边缘到达视图的上边缘。

这是一个plunker:https://embed.plnkr.co/mlez1dxjr87fnbhxq1ym/

它是这样使用的:

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

这是指令:

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();
  }
}

马丁·克雷默(Martin Cremer

import {
    ElementRef, Output, Directive, AfterViewInit, OnDestroy, EventEmitter
} from '@angular/core';
import { Subscription } from 'rxjs';
import { fromEvent } from 'rxjs';
import { startWith } from 'rxjs/operators';
@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 = fromEvent(window, 'scroll').pipe(startWith(null))
            .subscribe(() => {
                this.saveScrollPos();
                this.checkVisibility();
            });
        this.subscriptionResize = fromEvent(window, 'resize').pipe(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();
    }
}

我已经创建了一个提供标志出现的基本组件,如果组件完全在视图中,或者它的上部边缘已经到达视图,则该组件变为true。上边缘。

@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();
  }
}

您可以简单地扩展此组件,并通过继承出现

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

如果您需要覆盖构造函数或生命周期曲线,请记住super((。

(编辑( plunker :https://embed.plnkr.co/yipa1mi1b9kvoexgy6hh/

(编辑(我将其变成下面另一个答案中的指令。

在特定组件中想要的简单方法:

@ViewChild('chatTeaser') chatTeaser: ElementRef;
@HostListener('window:scroll')
checkScroll() {
    const scrollPosition = window.pageYOffset + window.innerHeight;
    if (this.chatTeaser && this.chatTeaser.nativeElement.offsetTop >= scrollPosition) {
        this.animateAvatars();
    }
}

和html:

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

准确地将元素的顶部滚动到函数时。如果您只想在查看完整的DIV时才调用该函数,则将DIV高度添加到this.chatTeaser.nativeElement.offsetTop

马丁·克雷默(Martin Cremer(给出的答案是完美的。

除非您希望使用SSR

Angular Universal
在Angular应用程序上使用它

我已经修改了现有的接受的答案,以在下面的SSR中工作

创建一个可注射的服务,以便能够在后端使用窗口对象
import { Injectable } from '@angular/core';
export interface ICustomWindow extends Window {
  __custom_global_stuff: string;
}
function getWindow (): any {
  return window;
}
@Injectable({
  providedIn: 'root',
})
export class WindowService {
  get nativeWindow (): ICustomWindow {
    return getWindow();
  }
}
现在,创建一个指令,以通知何时在可查看区域
import { Directive, ElementRef, EventEmitter, HostListener, Output } from '@angular/core';
import { WindowService } from './window.service';
@Directive({
  selector: '[appear]'
})
export class AppearDirective {
  windowHeight: number = 0;
  elementHeight: number = 0;
  elementPos: number = 0;
  @Output()
  appear: EventEmitter<boolean>;
  constructor(
    private element: ElementRef,
    private window: WindowService
  ) {
    this.appear = new EventEmitter<boolean>();
  }
  checkVisible() {
    if (this.elementPos < this.window.nativeWindow.scrollY + this.windowHeight) {
      this.appear.emit(true);
      this.appear.complete();
    }
  }
  @HostListener('window:scroll', [])
  onScroll() {
    this.checkVisible();
  }
  @HostListener('window:load', [])
  onLoad() {
    this.windowHeight = (this.window.nativeWindow.innerHeight);
    this.elementHeight = (this.element.nativeElement as HTMLElement).offsetHeight;
    this.elementPos = (this.element.nativeElement as HTMLElement).offsetTop;
    this.checkVisible();
  }
  @HostListener('window:resize', [])
  onResize() {
    this.windowHeight = (this.window.nativeWindow.innerHeight);
    this.elementHeight = (this.element.nativeElement as HTMLElement).offsetHeight;
    this.elementPos = (this.element.nativeElement as HTMLElement).offsetTop;
    this.checkVisible();
  }
}
在组件中创建一个新功能

onAppear() {
    // TODO: do something
}
将指令添加到您的元素
<!-- ... -->
<h2 (appear)="onAppear()">Visible</h2>
<!-- ... -->

有一个较新的API,旨在处理此确切问题:intersevtionobserver。使用此功能将使您可以消除所有手动偏移计算&amp;保持当地状态。这是一个使用此API的简单示例:

import { AfterViewInit, Directive, ElementRef, EventEmitter, OnDestroy, Output } from '@angular/core';
/**
 * @description
 * Emits the `appear` event when the element comes into view in the viewport.
 *
 */
@Directive({
    selector: '[visibleSpy]',
})
export class OnVisibleDirective implements AfterViewInit, OnDestroy {
    @Output() appear = new EventEmitter<void>();
    private observer: IntersectionObserver;
    constructor(private element: ElementRef) {}
    ngAfterViewInit() {
        const options = {
            root: null,
            rootMargin: '0px',
            threshold: 0,
        };
        this.observer = new IntersectionObserver((entries) => {
            entries.forEach((entry) => {
                if (entry.isIntersecting) {
                    this.appear.next();
                }
            });
        }, options);
        this.observer.observe(this.element.nativeElement);
    }
    ngOnDestroy() {
        this.observer.disconnect();
    }
}

这是无限卷轴的简单示例;当元素出现在视口内时,它会触发handleScrollEvent()

内部item-grid.component.html

<span [ngClass]="{hidden: curpage==maxpage}" (window:scroll)="handleScrollEvent()" (window:resize)="handleScrollEvent()" #loadmoreBtn (click)="handleLoadMore()">Load more</span>

item-grid.component.ts

@ViewChild('loadmoreBtn') loadmoreBtn: ElementRef;
curpage: number;
maxpage: number;
ngOnInit() {
  this.curpage = 1;
  this.maxpage = 5;
}
handleScrollEvent() {
  const { x, y } = this.loadmoreBtn.nativeElement.getBoundingClientRect();
  if (y < window.innerHeight && this.maxpage > this.curpage) {
    this.curpage++;
  }
}

最新更新