References
Scenario
When I create landing page with angular 2+, animation is run immediately even component is not show. After contact with google =)), well, I found that is normal case we meet went using css animation. This is why I create this post. Enjoy!
Requirement: Read about intersection observer api and angular animation above.
Thank @Epenance because he creates the lib NGX Animate In. I took a reference from his code and created this post with some of my own opinions and my scenario.
Implement Code
// animations/directives/animate-after-appear.directive.ts import { Directive, Input, ElementRef, OnInit } from '@angular/core'; import { animate, AnimationBuilder, AnimationFactory, AnimationMetadata, AnimationPlayer, style, } from '@angular/animations'; import { IntersectionObserverService } from '../services/intersection-observer.service'; import * as buildInAnmiations from '../animations'; @Directive({ selector: '[animateAfterAppear]', }) export class AnimateAfterAppearDirective implements OnInit { @Input() animateAfterAppear: 'fadeIn' | 'fadeInDown'; @Input() animationOptions: any; // custom your own animations player?: AnimationPlayer; constructor( private _observer: IntersectionObserverService, private el: ElementRef, private animationBuilder: AnimationBuilder ) {} ngOnInit() { let animation: AnimationFactory; if ( !!this.animationOptions !== null && this.animationOptions !== undefined ) { animation = this.animationBuilder.build(this.animationOptions); } if ( !!this.animateAfterAppear && !!buildInAnmiations[this.animateAfterAppear] ) { console.log('build in', this.animateAfterAppear); animation = this.animationBuilder.build( buildInAnmiations[this.animateAfterAppear] ); } else { animation = this.animationBuilder.build([ style({ opacity: 0, transform: 'translateX(-100px)' }), animate( '1200ms cubic-bezier(0.35, 0, 0.25, 1)', style({ opacity: 1, transform: 'translateX(0)' }) ), ]); } if (this._observer.isSupported()) { this.player = animation.create(this.el.nativeElement); this.player.init(); const callback = this.startAnimating.bind(this); this._observer.addTarget(this.el.nativeElement, callback); } } /** * Builds and triggers the animation * when it enters the viewport * @param {boolean} inViewport */ startAnimating(inViewport?: boolean, element?: Element): void { console.log('start animating'); if (inViewport) { this.player?.play(); } } }
// animations/services/intersection-observer.service.ts import { Injectable, Optional } from '@angular/core'; export class IntersectionObserverServiceConfig { root?: Element | null; rootMargin?: string; threshold?: number | number[]; } export type CallbackType = (inViewport?: boolean, element?: Element) => void; export interface WatchedItem { element: Element; callback: CallbackType; } @Injectable() export class IntersectionObserverService { options: IntersectionObserverServiceConfig = { rootMargin: '0px', threshold: 0.1, }; // where Intersection is support supported = false; watching: Array<WatchedItem> = []; observer: IntersectionObserver | null; /** * Assigns the user config if they wish to * override the defaults by using forRoot * @param {IntersectionObserverServiceConfig} config */ constructor(@Optional() config: IntersectionObserverServiceConfig) { this.supported = 'IntersectionObserver' in window && 'IntersectionObserverEntry' in window; if (config) { this.options = { ...this.options, ...config }; } this.observer = this.supported ? new IntersectionObserver(this.handleEvent.bind(this), this.options) : null; } /** * Handles events made by the observer * @param {IntersectionObserverEntry[]} entries */ handleEvent(entries: IntersectionObserverEntry[]): void { entries.forEach((entry: IntersectionObserverEntry) => { const target = this.watching.find((element) => { return element.element === entry.target; }); if (entry.isIntersecting) { // un observe after intersecting this.observer?.unobserve(entry.target); // callback target?.callback(true, entry.target); // remove item in watching list this.watching = this.watching.filter( (element) => element.element !== entry.target ); } }); } /** * Adds the target to our array so we can call its * call back when it enters the viewport * @param {Element} element * @param {CallbackType} callback */ addTarget(element: Element, callback: CallbackType): void { this.observer?.observe(element); this.watching.push({ element: element, callback: callback, }); } isSupported() { return this.supported; } }
// animations/animations/fade.ts import { animate, AnimationMetadata, state, style } from '@angular/animations'; export const fadeIn: AnimationMetadata[] = [ style({ opacity: 0 }), animate('1000ms', style({ opacity: 1 })), ]; export const fadeInDown: AnimationMetadata[] = [ style({ opacity: 0, transform: 'translate3d(0, -20%, 0)' }), animate('500ms', style({ opacity: 1, transform: 'translate3d(0, 0, 0)' })), ]; export const fadeInUp: AnimationMetadata[] = [ style({ opacity: 0, transform: 'translate3d(0, 20%, 0)' }), animate('500ms', style({ opacity: 1, transform: 'translate3d(0, 0, 0)' })), ]; export const fadeInLeft: AnimationMetadata[] = [ style({ opacity: 0, transform: 'translate3d(-10%, 0, 0)' }), animate('500ms', style({ opacity: 1, transform: 'translate3d(0, 0, 0)' })), ]; export const fadeInRight: AnimationMetadata[] = [ style({ opacity: 0, transform: 'translate3d(10%, 0, 0)' }), animate('500ms', style({ opacity: 1, transform: 'translate3d(0, 0, 0)' })), ];
// animations/animations/index.ts export * from './fade';
Top comments (0)