En este artículo voy a ilustrar cómo crear una directiva angular muy simple que realiza un seguimiento del estado de visibilidad de un elemento, o en otras palabras, cuando entra y sale de la ventana gráfica. ¡Espero que este sea un ejercicio agradable y quizás útil!
Para hacer esto, usaremos la API JavaScript IntersectionObserver que está disponible en los navegadores modernos.
Queremos utilizar la Directiva así:
I'm being observed! Can you see me yet?
La salida tendrá esta forma:
type VisibilityChange = | { isVisible: true; target: HTMLElement; } | { isVisible: false; target: HTMLElement | undefined; };
Tener un objetivo indefinido significará que el elemento ha sido eliminado del DOM (por ejemplo, por un @if).
Nuestra directiva simplemente monitoreará un elemento, no cambiará la estructura DOM: será una Directiva de atributos.
@Directive({ selector: "[visibility]", standalone: true }) export class VisibilityDirective implements OnInit, OnChanges, AfterViewInit, OnDestroy { private element = inject(ElementRef); /** * Emits after the view is initialized. */ private afterViewInit$ = new Subject(); /** * The IntersectionObserver for this element. */ private observer: IntersectionObserver | undefined; /** * Last known visibility for this element. * Initially, we don't know. */ private isVisible: boolean = undefined; /** * If false, once the element becomes visible there will be one emission and then nothing. * If true, the directive continuously listens to the element and emits whenever it becomes visible or not visible. */ visibilityMonitor = input(false); /** * Notifies the listener when the element has become visible. * If "visibilityMonitor" is true, it continuously notifies the listener when the element goes in/out of view. */ visibilityChange = output (); }
En el código anterior ves:
Y, naturalmente, inyectamos ElementRef para capturar el elemento DOM al que aplicamos nuestra directiva.
Antes de escribir el método principal, ocupémonos del ciclo de vida de la directiva.
ngOnInit(): void { this.reconnectObserver(); } ngOnChanges(): void { this.reconnectObserver(); } ngAfterViewInit(): void { this.afterViewInit$.next(); } ngOnDestroy(): void { // Disconnect and if visibilityMonitor is true, notify the listener this.disconnectObserver(); if (this.visibilityMonitor) { this.visibilityChange.emit({ isVisible: false, target: undefined }); } } private reconnectObserver(): void {} private disconnectObserver(): void {}
Esto es lo que sucede:
Este es el corazón de nuestra directiva. ¡Nuestro método reconnectObserver será el indicado para comenzar a observar! Será algo como esto:
private reconnectObserver(): void { // Disconnect an existing observer this.disconnectObserver(); // Sets up a new observer this.observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { const { isIntersecting: isVisible, target } = entry; const hasChangedVisibility = isVisible !== this.isVisible; const shouldEmit = isVisible || (!isVisible && this.visibilityMonitor); if (hasChangedVisibility && shouldEmit) { this.visibilityChange.emit({ isVisible, target: target as HTMLElement }); this.isVisible = isVisible; } // If visilibilyMonitor is false, once the element is visible we stop. if (isVisible && !this.visibilityMonitor) { observer.disconnect(); } }); }); // Start observing once the view is initialized this.afterViewInit$.subscribe(() => { this.observer?.observe(this.element.nativeElement); }); }
Créeme, ¡no es tan complicado como parece! Aquí está el mecanismo:
Por último, implementemos el método que desconecta al observador, muy fácil:
private disconnectObserver(): void { if (this.observer) { this.observer.disconnect(); this.observer = undefined; } }
Aquí está la directiva completa. Esto fue solo un ejercicio, ¡así que puedes cambiarlo como quieras!
type VisibilityChange = | { isVisible: true; target: HTMLElement; } | { isVisible: false; target: HTMLElement | undefined; }; @Directive({ selector: "[visibility]", standalone: true }) export class VisibilityDirective implements OnChanges, OnInit, AfterViewInit, OnDestroy { private element = inject(ElementRef); /** * Emits after the view is initialized. */ private afterViewInit$ = new Subject(); /** * The IntersectionObserver for this element. */ private observer: IntersectionObserver | undefined; /** * Last known visibility for this element. * Initially, we don't know. */ private isVisible: boolean = undefined; /** * If false, once the element becomes visible there will be one emission and then nothing. * If true, the directive continuously listens to the element and emits whenever it becomes visible or not visible. */ visibilityMonitor = input(false); /** * Notifies the listener when the element has become visible. * If "visibilityMonitor" is true, it continuously notifies the listener when the element goes in/out of view. */ visibilityChange = output (); ngOnInit(): void { this.reconnectObserver(); } ngOnChanges(): void { this.reconnectObserver(); } ngAfterViewInit(): void { this.afterViewInit$.next(true); } ngOnDestroy(): void { // Disconnect and if visibilityMonitor is true, notify the listener this.disconnectObserver(); if (this.visibilityMonitor) { this.visibilityChange.emit({ isVisible: false, target: undefined }); } } private reconnectObserver(): void { // Disconnect an existing observer this.disconnectObserver(); // Sets up a new observer this.observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { const { isIntersecting: isVisible, target } = entry; const hasChangedVisibility = isVisible !== this.isVisible; const shouldEmit = isVisible || (!isVisible && this.visibilityMonitor); if (hasChangedVisibility && shouldEmit) { this.visibilityChange.emit({ isVisible, target: target as HTMLElement }); this.isVisible = isVisible; } // If visilibilyMonitor is false, once the element is visible we stop. if (isVisible && !this.visibilityMonitor) { observer.disconnect(); } }); }); // Start observing once the view is initialized this.afterViewInit$.subscribe(() => { this.observer?.observe(this.element.nativeElement); }); } private disconnectObserver(): void { if (this.observer) { this.observer.disconnect(); this.observer = undefined; } } }
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3