From 624c511690c8d1f0c94ea3a1f331ec56d65175ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87etin?= <69278826+cetincakiroglu@users.noreply.github.com> Date: Thu, 20 Oct 2022 18:14:20 +0300 Subject: [PATCH] Fixed #12057 - Add animation directive --- src/app/components/animate/animate.ts | 178 +++++++++++++++++++++ src/app/components/animate/ng-package.json | 6 + src/app/components/animate/public_api.ts | 1 + 3 files changed, 185 insertions(+) create mode 100644 src/app/components/animate/animate.ts create mode 100644 src/app/components/animate/ng-package.json create mode 100644 src/app/components/animate/public_api.ts diff --git a/src/app/components/animate/animate.ts b/src/app/components/animate/animate.ts new file mode 100644 index 00000000000..7acd36e1b51 --- /dev/null +++ b/src/app/components/animate/animate.ts @@ -0,0 +1,178 @@ +import { NgModule, Directive, ElementRef, Input, Renderer2, Inject } from '@angular/core'; +import { CommonModule, DOCUMENT } from '@angular/common'; +import { DomHandler } from '../dom/domhandler'; + +@Directive({ + selector: '[pAnimate]', + host: { + '[class.p-animate]': 'true' + } +}) +export class Animate { + + @Input('parent') parentElement: any; + + @Input() enterClass: string; + + @Input() leaveClass: string; + + documentScrollListener: Function | null = null; + + loadListener: Function = () => { }; + + entered: boolean; + + observer: IntersectionObserver; + + loaded: boolean; + + scroll: boolean; + + constructor(@Inject(DOCUMENT) private document: Document, private host: ElementRef, public el: ElementRef, public renderer: Renderer2) { } + + ngOnInit() { + if (this.isImage()) { + if (this.isInViewport()) { + this.enter(); + } + this.bindLoadListener(); + } + } + + constructIntersectionObserver() { + const options = { + root: null, + rootMargin: '0px', + treshold: 1.0 + } + + this.observer = new IntersectionObserver((el) => this.isVisible(el), options); + this.observer.observe(this.host.nativeElement); + } + + isVisible(element) { + const [intersectionObserverEntry] = element; + this.entered = intersectionObserverEntry.isIntersecting; + + if (this.entered || this.isInViewport()) { + this.blockOverflow(); + } + if (!this.scroll && !this.isInViewport()) { + this.unblockOverflow(); + } + } + + isInViewport() { + let rect = this.host.nativeElement.getBoundingClientRect(); + + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= ((window.innerHeight + rect.height) || this.document.documentElement.clientHeight) && + rect.right <= (window.innerWidth || this.document.documentElement.clientWidth) + ); + } + + isImage(): boolean { + return this.el.nativeElement.tagName === 'IMG'; + } + + animate() { + if (this.loaded) { + if (this.isInViewport() && !this.entered) { + this.enter(); + } + + if (this.isInViewport() && this.entered) { + this.enter(); + } + + if (!this.isInViewport() && !this.entered) { + this.leave(); + } + } + } + + enter() { + this.host.nativeElement.style.visibility = 'visible'; + DomHandler.addClass(this.host.nativeElement, this.enterClass); + } + + leave() { + this.host.nativeElement.style.visibility = 'hidden'; + if (this.leaveClass) { + DomHandler.addClass(this.host.nativeElement, this.leaveClass); + } + DomHandler.removeClass(this.host.nativeElement, this.enterClass); + } + + blockOverflow() { + if (this.parentElement.nativeElement) { + DomHandler.addClass(this.parentElement.nativeElement, 'overflow-x-hidden'); + } + if (!this.parentElement.nativeElement && this.parentElement) { + DomHandler.addClass(this.parentElement, 'overflow-x-hidden'); + } + } + + unblockOverflow() { + if (this.parentElement.nativeElement) { + DomHandler.removeClass(this.parentElement.nativeElement, 'overflow-x-hidden'); + } + if (!this.parentElement.nativeElement && this.parentElement) { + DomHandler.removeClass(this.parentElement, 'overflow-x-hidden'); + } + } + + bindDocumentScrollListener() { + if (!this.documentScrollListener) { + this.documentScrollListener = this.renderer.listen(window, 'scroll', () => { + this.scroll = true; + if (!this.observer) { + this.constructIntersectionObserver(); + } + this.animate(); + }) + } + } + + unbindDocumentScrollListener() { + if (this.documentScrollListener) { + this.scroll = false; + this.documentScrollListener(); + this.documentScrollListener = null; + } + } + + bindLoadListener() { + this.loadListener = this.renderer.listen(this.el.nativeElement, 'load', () => { + if (!this.loaded) { + this.animate(); + } + if (!this.documentScrollListener) { + this.bindDocumentScrollListener(); + } + this.loaded = true; + }); + } + + unbindLoadListener() { + if (this.loadListener) { + this.loadListener(); + this.loadListener = null; + } + } + + ngOnDestroy() { + this.unbindDocumentScrollListener(); + this.unbindLoadListener(); + this.unblockOverflow(); + } +} + +@NgModule({ + imports: [CommonModule], + exports: [Animate], + declarations: [Animate] +}) +export class AnimateModule { } diff --git a/src/app/components/animate/ng-package.json b/src/app/components/animate/ng-package.json new file mode 100644 index 00000000000..0e529e387d7 --- /dev/null +++ b/src/app/components/animate/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} \ No newline at end of file diff --git a/src/app/components/animate/public_api.ts b/src/app/components/animate/public_api.ts new file mode 100644 index 00000000000..a4e45474233 --- /dev/null +++ b/src/app/components/animate/public_api.ts @@ -0,0 +1 @@ +export * from './animate'; \ No newline at end of file