I spent days trying to figure this out. Being a newbie I tried many things and none of them work. Finally, I have a solution so I will post it here.
There are 2 steps:
- Animate when things appear.
- Make things appear when scrolling.
Part 1: I found out these two great tutorials for newbies:
- The most basic one
- The one that actually animates when stuff appears
Part 2: I simply find the solution in this answer
Part 1 Step by Step:
- Add the line
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
to /src/app/app.module.ts
and then also:
@NgModule({
// Other arrays removed
imports: [
// Other imports
BrowserAnimationsModule
],
})
- In the component.ts you want to animate, add:
import { trigger,state,style,transition,animate } from '@angular/animations';
And then:
@Component({
// Here goes the selector and templates and etc.
animations: [
trigger('fadeInOut', [
state('void', style({
opacity: 0
})),
transition('void <=> *', animate(1000)),
]),
]
})
- Finally, in the HTML item you want to animate, add
[@fadeInOut]
.
If everything was done correctly, you should now have an animation (but it happens as soon as the webpage loads and not when you scroll.
Part 2 Step by Step:
- Create a file .ts like for example
appear.ts
and copy-paste this code:
import {
ElementRef, Output, Directive, AfterViewInit, OnDestroy, EventEmitter
} from '@angular/core';
import { Observable, Subscription, fromEvent } from 'rxjs';
import { startWith } from 'rxjs/operators';
//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 = 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();
}
}
- Import it using
import {AppearDirective} from './timeline/appear';
and add it to the imports as:
@NgModule({
declarations: [
// Other declarations
AppearDirective
],
// Imports and stuff
- Somewhere in the class do:
hasAppeared : boolean = false;
onAppear(){
this.hasAppeared = true;
console.log("I have appeared!"); // This is a good idea for debugging
}
- Finally, in the HTML add the two following:
(appear)="onAppear()" *ngIf="hasAppeared"
You can check this is working by checking the console for the message "I have appeared!".