首頁 >web前端 >js教程 >Angular LAB:讓我們建立一個可見性指令

Angular LAB:讓我們建立一個可見性指令

Barbara Streisand
Barbara Streisand原創
2024-10-09 08:21:02963瀏覽

Angular LAB: let

在本文中,我將說明如何創建一個非常簡單的 Angular 指令來追蹤元素的可見性狀態,或者換句話說,當它進入和退出時視口。我希望這會是一個很好的、也許有用的練習!

為了做到這一點,我們將使用現代瀏覽器中可用的 IntersectionObserver JavaScript API。

我們想要實現什麼

我們想像這樣使用指令:

<p
  visibility
  [visibilityMonitor]="true"
  (visibilityChange)="onVisibilityChange($event)"
>
  I'm being observed! Can you see me yet?
</p>
  • 可見性是我們自訂指令的選擇器
  • visibilityMonitor 是一個可選輸入,指定是否繼續觀察元素(如果為 false,則在進入視口時停止監視)
  • visibilityChange 會通知我們

輸出將具有以下形狀:

type VisibilityChange =
  | {
      isVisible: true;
      target: HTMLElement;
    }
  | {
      isVisible: false;
      target: HTMLElement | undefined;
    };

擁有未定義的目標表示該元素已從 DOM 中刪除(例如,透過 @if)。

指令的製定

我們的指令只會監視一個元素,它不會改變 DOM 結構:它將是一個 屬性指令.

@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<void>();

  /**
   * 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<VisibilityChange>();
}

在上面的程式碼中您會看到:

  • 我們之前討論過的輸入和輸出
  • 一個名為 afterViewInit$ 的屬性(一個 Observable),它將充當 ngAfterViewInit 生命週期鉤子的響應式對應項
  • 一個名為observer的屬性,它將儲存負責監視我們元素的IntersectionObserver
  • 一個名為 isVisibile 的屬性,它將儲存最後的可見性狀態,以避免連續兩次重新發出相同的狀態

自然地,我們注入 ElementRef 來取得我們應用指令的 DOM 元素。

在寫 main 方法之前,讓我們先處理一下指令的生命週期。

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 {}

現在發生的事情是這樣的:

  • 在 ngOnInit 和 ngOnChanges 中,我們重新啟動觀察者。這是為了使指令具有反應性:如果輸入發生變化,指令將開始表現不同。請注意,即使 ngOnChanges 也在 ngOnInit 之前運行,我們仍然需要 ngOnInit,因為如果模板中沒有輸入,ngOnChanges 不會運行!
  • 當視圖初始化時,我們會觸發主題,我們將在幾秒鐘內完成此操作
  • 當指令被銷毀時,我們會斷開觀察者的連接,以避免記憶體洩漏。最後,如果開發人員要求,我們會透過發出未定義的元素來通知該元素已從 DOM 中刪除。

路口觀察者

這是我們指令的核心。我們的 reconnectObserver 方法將是開始觀察的方法!它會是這樣的:

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);
    });
  }

相信我,它並不像看起來那麼複雜!機轉如下:

  • 首先我們斷開觀察者(如果它已經在運行)
  • 我們建立一個 IntersectionObserver 並定義它的行為。這些條目將包含受監視的元素,因此它將包含我們的元素。屬性 isIntersecting 將指示元素的可見性是否已更改:我們將其與先前的狀態(我們的屬性)進行比較,如果到期,我們將發出。然後我們將新狀態儲存在我們的屬性中以供稍後使用。
  • 如果visibilityMonitor為假,一旦元素變得可見,我們就會斷開觀察者的連結:它的工作就完成了!
  • 然後我們必須透過傳遞元素來啟動觀察者,因此我們等待視圖初始化才能執行此操作。

最後,讓我們實作斷開觀察者連結的方法,簡單:

 private disconnectObserver(): void {
    if (this.observer) {
      this.observer.disconnect();
      this.observer = undefined;
    }
  }

最終程式碼

這是完整的指令。這只是一個練習,所以可以隨意將其更改為您喜歡的任何內容!

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;
    }
  }
}

以上是Angular LAB:讓我們建立一個可見性指令的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn