首页 > web前端 > js教程 > 正文

Angular LAB:让我们创建一个可见性指令

Barbara Streisand
发布: 2024-10-09 08:21:02
原创
848 人浏览过

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中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责声明 Sitemap
PHP中文网:公益在线PHP培训,帮助PHP学习者快速成长!