pull/4704/head
mertsincan 2023-10-26 01:54:11 +01:00
parent 12333edd7c
commit 249cfbd68f
5 changed files with 127 additions and 48 deletions

View File

@ -23,6 +23,25 @@ export interface AnimateOnScrollOptions {
* AnimateOnScroll scroll to add when item begins to get hidden. * AnimateOnScroll scroll to add when item begins to get hidden.
*/ */
leaveClass?: string | undefined; leaveClass?: string | undefined;
/**
* Specifies the `root` option of the IntersectionObserver API
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/IntersectionObserver/root)
*/
root?: Element | Document | null;
/**
* Specifies the `rootMargin` option of the IntersectionObserver API
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/IntersectionObserver/rootMargin)
*/
rootMargin?: string;
/**
* Specifies the `threshold` option of the IntersectionObserver API
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/IntersectionObserver/thresholds)
*/
threshold?: ReadonlyArray<number>;
/**
* Whether the `enterClass` animation will run if the target is in the viewport when the page is loaded.
*/
animateOnLoad?: boolean;
/** /**
* Used to pass attributes to DOM elements inside the component. * Used to pass attributes to DOM elements inside the component.
* @type {AnimateOnScrollDirectivePassThroughOptions} * @type {AnimateOnScrollDirectivePassThroughOptions}

View File

@ -2,57 +2,102 @@ import { DomHandler } from 'primevue/utils';
import BaseAnimateOnScroll from './BaseAnimateOnScroll'; import BaseAnimateOnScroll from './BaseAnimateOnScroll';
const AnimateOnScroll = BaseAnimateOnScroll.extend('animateonscroll', { const AnimateOnScroll = BaseAnimateOnScroll.extend('animateonscroll', {
mounted(el, binding) { created() {
el.setAttribute('data-pd-animateonscroll', true); this.$value = this.$value || {};
!this.isUnstyled() && DomHandler.addClass(el, 'p-animate'); this.$el.style.opacity = this.$value.enterClass ? '0' : '';
},
mounted() {
this.$el.setAttribute('data-pd-animateonscroll', true);
this.bindIntersectionObserver(el, binding); this.bindIntersectionObserver();
}, },
unmounted(el) { unmounted() {
this.unbindIntersectionObserver(el); this.unbindAnimationEvents();
clearTimeout(this.timeout); this.unbindIntersectionObserver();
}, },
timeout: null, observer: undefined,
observer: null, resetObserver: undefined,
isObserverActive: false,
animationState: undefined,
animationEndListener: undefined,
methods: { methods: {
bindIntersectionObserver(el, binding) { bindAnimationEvents() {
const options = { if (!this.animationEndListener) {
root: null, this.animationEndListener = () => {
rootMargin: '0px', DomHandler.removeMultipleClasses(this.$el, [this.$value.enterClass, this.$value.leaveClass]);
threshold: 1.0 !this.$modifiers.once && this.resetObserver.observe(this.$el);
this.unbindAnimationEvents();
}; };
this.observer = new IntersectionObserver((element) => this.isVisible(element, el, binding), options); this.$el.addEventListener('animationend', this.animationEndListener);
this.observer.observe(el); }
}, },
isVisible(target, el, binding) { bindIntersectionObserver() {
const [intersectionObserverEntry] = target; const { root, rootMargin, threshold = 0.5 } = this.$value;
const options = { root, rootMargin, threshold };
intersectionObserverEntry.isIntersecting ? this.enter(el, binding) : this.leave(el, binding); // States
}, this.observer = new IntersectionObserver(([entry]) => {
enter(el, binding) { if (this.isObserverActive) {
el.style.visibility = 'visible'; if (entry.boundingClientRect.top > 0) {
DomHandler.addMultipleClasses(el, binding.value.enterClass); entry.isIntersecting ? this.enter() : this.leave();
}
binding.modifiers.once && this.unbindIntersectionObserver(el); } else if (entry.isIntersecting) {
}, this.$value.animateOnLoad ? this.enter() : (this.$el.style.opacity = '');
leave(el, binding) {
DomHandler.removeClass(el, binding.value.enterClass);
if (binding.value.leaveClass) {
DomHandler.addMultipleClasses(el, binding.value.leaveClass);
} }
const animationDuration = el.style.animationDuration || 500; this.isObserverActive = true;
}, options);
this.timeout = setTimeout(() => { setTimeout(() => this.observer.observe(this.$el), 0);
el.style.visibility = 'hidden';
}, animationDuration); // Reset
}, this.resetObserver = new IntersectionObserver(
unbindIntersectionObserver(el) { ([entry]) => {
if (this.observer) { if (entry.boundingClientRect.top > 0 && !entry.isIntersecting) {
this.observer.unobserve(el); this.$el.style.opacity = this.$value.enterClass ? '0' : '';
DomHandler.removeMultipleClasses(this.$el, [this.$value.enterClass, this.$value.leaveClass]);
this.resetObserver.unobserve(this.$el);
} }
this.animationState = undefined;
},
{ ...options, threshold: 0 }
);
},
enter() {
if (this.animationState !== 'enter' && this.$value.enterClass) {
this.$el.style.opacity = '';
DomHandler.removeMultipleClasses(this.$el, this.$value.leaveClass);
DomHandler.addMultipleClasses(this.$el, this.$value.enterClass);
this.$modifiers.once && this.unbindIntersectionObserver(this.$el);
this.bindAnimationEvents();
this.animationState = 'enter';
}
},
leave() {
if (this.animationState !== 'leave' && this.$value.leaveClass) {
this.$el.style.opacity = this.$value.enterClass ? '0' : '';
DomHandler.removeMultipleClasses(this.$el, this.$value.enterClass);
DomHandler.addMultipleClasses(this.$el, this.$value.leaveClass);
this.bindAnimationEvents();
this.animationState = 'leave';
}
},
unbindAnimationEvents() {
if (this.animationEndListener) {
this.$el.removeEventListener('animationend', this.animationEndListener);
this.animationEndListener = undefined;
}
},
unbindIntersectionObserver() {
this.observer?.unobserve(this.$el);
this.resetObserver?.unobserve(this.$el);
this.isObserverActive = false;
} }
} }
}); });

View File

@ -94,7 +94,9 @@ const BaseDirective = {
$name: name, $name: name,
$host: el, $host: el,
$binding: binding, $binding: binding,
$el: $prevInstance['$el'] || undefined, $modifiers: binding?.modifiers,
$value: binding?.value,
$el: $prevInstance['$el'] || el || undefined,
$style: { classes: undefined, inlineStyles: undefined, loadStyle: () => {}, ...options?.style }, $style: { classes: undefined, inlineStyles: undefined, loadStyle: () => {}, ...options?.style },
$config: config, $config: config,
/* computed instance variables */ /* computed instance variables */

View File

@ -126,9 +126,21 @@ export default {
return -1; return -1;
}, },
addMultipleClasses(element, className) { addMultipleClasses(element, classNames) {
if (element && className) { if (element && classNames) {
className.split(' ').forEach((style) => this.addClass(element, style)); [classNames]
.flat()
.filter(Boolean)
.forEach((cNames) => cNames.split(' ').forEach((className) => this.addClass(element, className)));
}
},
removeMultipleClasses(element, classNames) {
if (element && classNames) {
[classNames]
.flat()
.filter(Boolean)
.forEach((cNames) => cNames.split(' ').forEach((className) => this.removeClass(element, className)));
} }
}, },

View File

@ -16,7 +16,8 @@ export declare class DomHandler {
static getViewport(): { width: number; height: number }; static getViewport(): { width: number; height: number };
static getOffset(el: HTMLElement): { top: any; left: any }; static getOffset(el: HTMLElement): { top: any; left: any };
static index(el: HTMLElement): number; static index(el: HTMLElement): number;
static addMultipleClasses(el: HTMLElement, className: string): void; static addMultipleClasses(el: HTMLElement, classNames: string | string[]): void;
static addRemoveClasses(el: HTMLElement, classNames: string | string[]): void;
static addClass(el: HTMLElement, className: string): void; static addClass(el: HTMLElement, className: string): void;
static removeClass(el: HTMLElement, className: string): void; static removeClass(el: HTMLElement, className: string): void;
static hasClass(el: HTMLElement, className: string): boolean; static hasClass(el: HTMLElement, className: string): boolean;